docs: comprehensively update and expand project documentation across various guides for improved clarity and detail.

This commit is contained in:
CJACK
2026-02-17 02:50:11 +08:00
parent a19f281229
commit d3ac698129
9 changed files with 1919 additions and 879 deletions

434
API.en.md
View File

@@ -1,81 +1,119 @@
# DS2API API Reference (Go Implementation)
# DS2API API Reference
Language: [中文](API.md) | [English](API.en.md)
This document describes the actual behavior of the current Go codebase.
---
## Table of Contents
- [Basics](#basics)
- [Authentication](#authentication)
- [Route Index](#route-index)
- [Health Endpoints](#health-endpoints)
- [OpenAI-Compatible API](#openai-compatible-api)
- [Claude-Compatible API](#claude-compatible-api)
- [Admin API](#admin-api)
- [Error Payloads](#error-payloads)
- [cURL Examples](#curl-examples)
---
## Basics
- Base URL: `http://localhost:5001` or your deployment domain
- Default content type: `application/json`
- Health probes: `GET /healthz`, `GET /readyz`
| Item | Details |
| --- | --- |
| Base URL | `http://localhost:5001` or your deployment domain |
| Default Content-Type | `application/json` |
| Health probes | `GET /healthz`, `GET /readyz` |
| CORS | Enabled (`Access-Control-Allow-Origin: *`) |
### Authentication Rules
---
Business endpoints (`/v1/*`, `/anthropic/*`) accept either:
## Authentication
1. `Authorization: Bearer <token>`
2. `x-api-key: <token>` (without `Bearer`)
### Business Endpoints (`/v1/*`, `/anthropic/*`)
Admin endpoints:
Two header formats accepted:
- `POST /admin/login` is public
- `GET /admin/verify` requires `Authorization: Bearer <jwt>` (JWT only)
- Other protected `/admin/*` endpoints accept:
- `Authorization: Bearer <jwt>`
- `Authorization: Bearer <admin_key>`
| Method | Example |
| --- | --- |
| Bearer Token | `Authorization: Bearer <token>` |
| API Key Header | `x-api-key: <token>` (no `Bearer` prefix) |
**Auth behavior**:
- Token is in `config.keys`**Managed account mode**: DS2API auto-selects an account via rotation
- Token is not in `config.keys`**Direct token mode**: treated as a DeepSeek token directly
**Optional header**: `X-Ds2-Target-Account: <email_or_mobile>` — Pin a specific managed account.
### Admin Endpoints (`/admin/*`)
| Endpoint | Auth |
| --- | --- |
| `POST /admin/login` | Public |
| `GET /admin/verify` | `Authorization: Bearer <jwt>` (JWT only) |
| Other `/admin/*` | `Authorization: Bearer <jwt>` or `Authorization: Bearer <admin_key>` |
---
## Route Index
| Method | Path | Description |
| --- | --- | --- |
| GET | `/healthz` | Liveness probe |
| GET | `/readyz` | Readiness probe |
| GET | `/v1/models` | OpenAI model list |
| POST | `/v1/chat/completions` | OpenAI chat completions |
| GET | `/anthropic/v1/models` | Claude model list |
| POST | `/anthropic/v1/messages` | Claude messages |
| POST | `/anthropic/v1/messages/count_tokens` | Claude token counting |
| POST | `/admin/login` | Admin login |
| GET | `/admin/verify` | Verify admin JWT |
| GET | `/admin/vercel/config` | Read preconfigured Vercel creds |
| GET | `/admin/config` | Read sanitized config |
| POST | `/admin/config` | Update config |
| POST | `/admin/keys` | Add API key |
| DELETE | `/admin/keys/{key}` | Delete API key |
| GET | `/admin/accounts` | Paginated account list |
| POST | `/admin/accounts` | Add account |
| DELETE | `/admin/accounts/{identifier}` | Delete account |
| GET | `/admin/queue/status` | Account queue status |
| POST | `/admin/accounts/test` | Test one account |
| POST | `/admin/accounts/test-all` | Test all accounts |
| POST | `/admin/import` | Batch import keys/accounts |
| POST | `/admin/test` | Test API through current service |
| POST | `/admin/vercel/sync` | Sync config to Vercel |
| GET | `/admin/vercel/status` | Vercel sync status |
| GET | `/admin/export` | Export config JSON/Base64 |
| Method | Path | Auth | Description |
| --- | --- | --- | --- |
| GET | `/healthz` | None | Liveness probe |
| GET | `/readyz` | None | Readiness probe |
| GET | `/v1/models` | None | OpenAI model list |
| POST | `/v1/chat/completions` | Business | OpenAI chat completions |
| GET | `/anthropic/v1/models` | None | Claude model list |
| POST | `/anthropic/v1/messages` | Business | Claude messages |
| POST | `/anthropic/v1/messages/count_tokens` | Business | Claude token counting |
| POST | `/admin/login` | None | Admin login |
| GET | `/admin/verify` | JWT | Verify admin JWT |
| GET | `/admin/vercel/config` | Admin | Read preconfigured Vercel creds |
| GET | `/admin/config` | Admin | Read sanitized config |
| POST | `/admin/config` | Admin | Update config |
| POST | `/admin/keys` | Admin | Add API key |
| DELETE | `/admin/keys/{key}` | Admin | Delete API key |
| GET | `/admin/accounts` | Admin | Paginated account list |
| POST | `/admin/accounts` | Admin | Add account |
| DELETE | `/admin/accounts/{identifier}` | Admin | Delete account |
| GET | `/admin/queue/status` | Admin | Account queue status |
| POST | `/admin/accounts/test` | Admin | Test one account |
| POST | `/admin/accounts/test-all` | Admin | Test all accounts |
| POST | `/admin/import` | Admin | Batch import keys/accounts |
| POST | `/admin/test` | Admin | Test API through service |
| POST | `/admin/vercel/sync` | Admin | Sync config to Vercel |
| GET | `/admin/vercel/status` | Admin | Vercel sync status |
| GET | `/admin/export` | Admin | Export config JSON/Base64 |
---
## Health Endpoints
### `GET /healthz`
```json
{"status":"ok"}
{"status": "ok"}
```
### `GET /readyz`
```json
{"status":"ready"}
{"status": "ready"}
```
---
## OpenAI-Compatible API
### `GET /v1/models`
No auth required.
No auth required. Returns supported models.
Example response:
**Response**:
```json
{
@@ -91,24 +129,24 @@ Example response:
### `POST /v1/chat/completions`
Headers:
**Headers**:
```http
Authorization: Bearer your-api-key
Content-Type: application/json
```
Core request fields:
**Request body**:
| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `model` | string | yes | `deepseek-chat` / `deepseek-reasoner` / `deepseek-chat-search` / `deepseek-reasoner-search` |
| `messages` | array | yes | OpenAI-style messages |
| `stream` | boolean | no | default `false` |
| `tools` | array | no | Function calling schema |
| `temperature`, etc. | any | no | accepted in request; final behavior depends on upstream |
| `model` | string | | `deepseek-chat` / `deepseek-reasoner` / `deepseek-chat-search` / `deepseek-reasoner-search` |
| `messages` | array | | OpenAI-style messages |
| `stream` | boolean | | Default `false` |
| `tools` | array | | Function calling schema |
| `temperature`, etc. | any | | Accepted but final behavior depends on upstream |
Non-stream example:
#### Non-Stream Response
```json
{
@@ -122,7 +160,7 @@ Non-stream example:
"message": {
"role": "assistant",
"content": "final response",
"reasoning_content": "reasoning trace"
"reasoning_content": "reasoning trace (reasoner models)"
},
"finish_reason": "stop"
}
@@ -138,17 +176,10 @@ Non-stream example:
}
```
### OpenAI Streaming (`stream=true`)
#### Streaming (`stream=true`)
SSE format: each frame is `data: <json>\n\n`, terminated by `data: [DONE]`.
- First delta may include `role: assistant`
- Reasoning models emit `delta.reasoning_content`
- Text emits `delta.content`
- Last chunk includes `finish_reason` and usage
Example:
```text
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant"},"index":0}]}
@@ -161,15 +192,18 @@ data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{},"index
data: [DONE]
```
### Tool Calls (Important)
**Field notes**:
When `tools` is present, DS2API injects a tool prompt and parses tool-call payloads.
- First delta includes `role: assistant`
- `deepseek-reasoner` / `deepseek-reasoner-search` models emit `delta.reasoning_content`
- Text emits `delta.content`
- Last chunk includes `finish_reason` and `usage`
- Non-stream: if detected, returns `message.tool_calls`, `finish_reason=tool_calls`, and `message.content=null`
- Stream: to avoid leaking raw tool-call JSON, DS2API buffers text first; if tool call is detected, only structured `delta.tool_calls` is emitted
- Stream `delta.tool_calls` is strict-client compatible: each tool call object includes `index` (starting from `0`)
#### Tool Calls
Tool-call response example:
When `tools` is present, DS2API performs anti-leak handling:
**Non-stream**: If detected, returns `message.tool_calls`, `finish_reason=tool_calls`, `message.content=null`.
```json
{
@@ -196,13 +230,17 @@ Tool-call response example:
}
```
**Stream**: DS2API buffers text first. If tool call detected → only structured `delta.tool_calls` (each with `index`); otherwise emits buffered text at once.
---
## Claude-Compatible API
### `GET /anthropic/v1/models`
No auth required.
Example response:
**Response**:
```json
{
@@ -217,7 +255,7 @@ Example response:
### `POST /anthropic/v1/messages`
Headers can be:
**Headers**:
```http
x-api-key: your-api-key
@@ -225,18 +263,18 @@ Content-Type: application/json
anthropic-version: 2023-06-01
```
Core request fields:
**Request body**:
| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `model` | string | yes | `claude-sonnet-4-20250514` / `-fast` / `-slow` |
| `messages` | array | yes | Claude-style messages |
| `max_tokens` | number | no | currently not strictly enforced by upstream bridge |
| `stream` | boolean | no | default `false` |
| `system` | string | no | optional system prompt |
| `tools` | array | no | Claude tool schema |
| `model` | string | | `claude-sonnet-4-20250514` / `-fast` / `-slow` |
| `messages` | array | | Claude-style messages |
| `max_tokens` | number | | Not strictly enforced by upstream bridge |
| `stream` | boolean | | Default `false` |
| `system` | string | | Optional system prompt |
| `tools` | array | | Claude tool schema |
Non-stream example:
#### Non-Stream Response
```json
{
@@ -258,11 +296,9 @@ Non-stream example:
If tool use is detected, `stop_reason` becomes `tool_use` and `content` contains `tool_use` blocks.
### Claude Streaming (`stream=true`)
#### Streaming (`stream=true`)
SSE uses paired `event:` + `data:` lines. Event type is also carried in JSON `type`.
Example:
SSE uses paired `event:` + `data:` lines. Event type is also in JSON `type`.
```text
event: message_start
@@ -287,15 +323,15 @@ event: message_stop
data: {"type":"message_stop"}
```
Notes:
**Notes**:
- Thinking-enabled models stream `thinking_delta`.
- `signature_delta` is not emitted because DeepSeek does not provide verifiable thinking signatures.
- In `tools` mode, the stream prioritizes avoiding raw tool JSON leakage and does not force `input_json_delta` partials.
- Thinking-enabled models (`-slow`) stream `thinking_delta`
- `signature_delta` is not emitted (DeepSeek does not provide verifiable thinking signatures)
- In `tools` mode, the stream avoids leaking raw tool JSON and does not force `input_json_delta`
### `POST /anthropic/v1/messages/count_tokens`
Request example:
**Request**:
```json
{
@@ -306,7 +342,7 @@ Request example:
}
```
Response example:
**Response**:
```json
{
@@ -314,11 +350,15 @@ Response example:
}
```
---
## Admin API
### `POST /admin/login`
Request:
Public endpoint.
**Request**:
```json
{
@@ -327,9 +367,9 @@ Request:
}
```
`expire_hours` is optional, default 24.
`expire_hours` is optional, default `24`.
Response:
**Response**:
```json
{
@@ -341,9 +381,9 @@ Response:
### `GET /admin/verify`
Header: `Authorization: Bearer <jwt>`
Requires JWT: `Authorization: Bearer <jwt>`
Response:
**Response**:
```json
{
@@ -355,6 +395,8 @@ Response:
### `GET /admin/vercel/config`
Returns Vercel preconfiguration status.
```json
{
"has_token": true,
@@ -365,7 +407,7 @@ Response:
### `GET /admin/config`
Sanitized config response:
Returns sanitized config.
```json
{
@@ -390,7 +432,7 @@ Sanitized config response:
Updatable fields: `keys`, `accounts`, `claude_mapping`.
Request example:
**Request**:
```json
{
@@ -408,29 +450,25 @@ Request example:
### `POST /admin/keys`
```json
{"key":"new-api-key"}
{"key": "new-api-key"}
```
Response:
```json
{"success":true,"total_keys":3}
```
**Response**: `{"success": true, "total_keys": 3}`
### `DELETE /admin/keys/{key}`
```json
{"success":true,"total_keys":2}
```
**Response**: `{"success": true, "total_keys": 2}`
### `GET /admin/accounts`
Query params:
**Query params**:
- `page` (default 1)
- `page_size` (default 10, max 100)
| Param | Default | Range |
| --- | --- | --- |
| `page` | `1` | ≥ 1 |
| `page_size` | `10` | 1100 |
Response:
**Response**:
```json
{
@@ -453,20 +491,16 @@ Response:
### `POST /admin/accounts`
```json
{"email":"user@example.com","password":"pwd"}
{"email": "user@example.com", "password": "pwd"}
```
```json
{"success":true,"total_accounts":6}
```
**Response**: `{"success": true, "total_accounts": 6}`
### `DELETE /admin/accounts/{identifier}`
`identifier` is email or mobile.
```json
{"success":true,"total_accounts":5}
```
**Response**: `{"success": true, "total_accounts": 5}`
### `GET /admin/queue/status`
@@ -482,29 +516,30 @@ Response:
}
```
Field notes:
- `max_inflight_per_account`: per-account in-flight limit (default `2`, override via env)
- `recommended_concurrency`: suggested client concurrency, dynamically computed as `account_count * max_inflight_per_account`
| Field | Description |
| --- | --- |
| `available` | Currently available accounts |
| `in_use` | Currently in-use accounts |
| `total` | Total accounts |
| `max_inflight_per_account` | Per-account inflight limit |
| `recommended_concurrency` | Suggested concurrency (`total × max_inflight_per_account`) |
### `POST /admin/accounts/test`
Request fields:
| Field | Required | Notes |
| --- | --- | --- |
| `identifier` | yes | email or mobile |
| `model` | no | default `deepseek-chat` |
| `message` | no | if empty, only session creation is tested |
| `identifier` | | email or mobile |
| `model` | | default `deepseek-chat` |
| `message` | | if empty, only session creation is tested |
Response example:
**Response**:
```json
{
"account": "user@example.com",
"success": true,
"response_time": 1240,
"message": "API 测试成功(仅会话创建)",
"message": "API test successful (session creation only)",
"model": "deepseek-chat"
}
```
@@ -518,21 +553,27 @@ Optional request field: `model`.
"total": 5,
"success": 4,
"failed": 1,
"results": []
"results": [...]
}
```
### `POST /admin/import`
Batch import keys and accounts.
**Request**:
```json
{
"keys": ["k1", "k2"],
"accounts": [
{"email":"user@example.com","password":"pwd","token":""}
{"email": "user@example.com", "password": "pwd", "token": ""}
]
}
```
**Response**:
```json
{
"success": true,
@@ -543,41 +584,41 @@ Optional request field: `model`.
### `POST /admin/test`
Optional request fields:
Test API availability through the service itself.
- `model` (default `deepseek-chat`)
- `message` (default `你好`)
- `api_key` (default first key in config)
| Field | Required | Default |
| --- | --- | --- |
| `model` | ❌ | `deepseek-chat` |
| `message` | ❌ | `你好` |
| `api_key` | ❌ | First key in config |
Response example:
**Response**:
```json
{
"success": true,
"status_code": 200,
"response": {"id":"..."}
"response": {"id": "..."}
}
```
### `POST /admin/vercel/sync`
Request fields:
| Field | Required | Notes |
| --- | --- | --- |
| `vercel_token` | no | if empty or `__USE_PRECONFIG__`, read env |
| `project_id` | no | fallback: `VERCEL_PROJECT_ID` |
| `team_id` | no | fallback: `VERCEL_TEAM_ID` |
| `auto_validate` | no | default `true` |
| `save_credentials` | no | default `true` |
| `vercel_token` | | If empty or `__USE_PRECONFIG__`, read env |
| `project_id` | | Fallback: `VERCEL_PROJECT_ID` |
| `team_id` | | Fallback: `VERCEL_TEAM_ID` |
| `auto_validate` | | Default `true` |
| `save_credentials` | | Default `true` |
Success response example:
**Success response**:
```json
{
"success": true,
"validated_accounts": 3,
"message": "配置已同步,正在重新部署...",
"message": "Config synced, redeploying...",
"deployment_url": "https://..."
}
```
@@ -588,7 +629,7 @@ Or manual deploy required:
{
"success": true,
"validated_accounts": 3,
"message": "配置已同步到 Vercel请手动触发重新部署",
"message": "Config synced to Vercel, please trigger redeploy manually",
"manual_deploy_required": true
}
```
@@ -612,19 +653,33 @@ Or manual deploy required:
}
```
---
## Error Payloads
Error payload formats are not fully unified in current code:
Error formats vary by module:
- OpenAI routes return: `{"error":{"message":"...","type":"..."}}`
- Claude routes often return: `{"error":{"type":"...","message":"..."}}`
- Admin routes often return: `{"detail":"..."}`
| Module | Format |
| --- | --- |
| OpenAI routes | `{"error": {"message": "...", "type": "..."}}` |
| Claude routes | `{"error": {"type": "...", "message": "..."}}` |
| Admin routes | `{"detail": "..."}` |
Clients should handle HTTP status code plus `error` / `detail` fields.
**Common status codes**:
| Code | Meaning |
| --- | --- |
| `401` | Authentication failed (invalid key/token, or expired admin JWT) |
| `429` | Too many requests (exceeded inflight + queue capacity) |
| `503` | Model unavailable or upstream error |
---
## cURL Examples
### OpenAI non-stream
### OpenAI Non-Stream
```bash
curl http://localhost:5001/v1/chat/completions \
@@ -637,7 +692,7 @@ curl http://localhost:5001/v1/chat/completions \
}'
```
### OpenAI stream
### OpenAI Stream
```bash
curl http://localhost:5001/v1/chat/completions \
@@ -650,7 +705,48 @@ curl http://localhost:5001/v1/chat/completions \
}'
```
### Claude
### OpenAI with Search
```bash
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat-search",
"messages": [{"role": "user", "content": "Latest news today"}],
"stream": true
}'
```
### OpenAI Tool Calling
```bash
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "What is the weather in Beijing?"}],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"]
}
}
}
]
}'
```
### Claude Non-Stream
```bash
curl http://localhost:5001/anthropic/v1/messages \
@@ -663,3 +759,39 @@ curl http://localhost:5001/anthropic/v1/messages \
"messages": [{"role": "user", "content": "Hello"}]
}'
```
### Claude Stream
```bash
curl http://localhost:5001/anthropic/v1/messages \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-sonnet-4-20250514-slow",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Explain relativity"}],
"stream": true
}'
```
### Admin Login
```bash
curl http://localhost:5001/admin/login \
-H "Content-Type: application/json" \
-d '{"admin_key": "admin"}'
```
### Pin Specific Account
```bash
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "X-Ds2-Target-Account: user@example.com" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "Hello"}]
}'
```

454
API.md
View File

@@ -1,85 +1,119 @@
# DS2API 接口文档Go 实现)
# DS2API 接口文档
语言 / Language: [中文](API.md) | [English](API.en.md)
本文档描述当前代码库的实际 API 行为Go 后端)
本文档描述当前 Go 代码库的实际 API 行为。
---
## 目录
- [基础信息](#基础信息)
- [鉴权规则](#鉴权规则)
- [路由总览](#路由总览)
- [健康检查](#健康检查)
- [OpenAI 兼容接口](#openai-兼容接口)
- [Claude 兼容接口](#claude-兼容接口)
- [Admin 接口](#admin-接口)
- [错误响应格式](#错误响应格式)
- [cURL 示例](#curl-示例)
---
## 基础信息
- Base URL`http://localhost:5001` 或你的部署域名
- 默认返回:`application/json`
- 健康检查:`GET /healthz``GET /readyz`
| 项目 | 说明 |
| --- | --- |
| Base URL | `http://localhost:5001` 或你的部署域名 |
| 默认 Content-Type | `application/json` |
| 健康检查 | `GET /healthz``GET /readyz` |
| CORS | 已启用(`Access-Control-Allow-Origin: *` |
### 鉴权规则
---
业务接口(`/v1/*``/anthropic/*`)支持两种传参:
## 鉴权规则
1. `Authorization: Bearer <token>`
2. `x-api-key: <token>`(无 `Bearer` 前缀)
### 业务接口(`/v1/*`、`/anthropic/*`
Admin 接口
支持两种传参方式
- `POST /admin/login` 无需鉴权
- `GET /admin/verify` 需要 `Authorization: Bearer <jwt>`(仅 JWT
- 其他 `/admin/*` 保护接口支持:
- `Authorization: Bearer <jwt>`
- `Authorization: Bearer <admin_key>`(直传管理密钥)
| 方式 | 示例 |
| --- | --- |
| Bearer Token | `Authorization: Bearer <token>` |
| API Key Header | `x-api-key: <token>`(无 `Bearer` 前缀) |
**鉴权行为**
- token 在 `config.keys` 中 → **托管账号模式**,自动轮询选择账号
- token 不在 `config.keys` 中 → **直通 token 模式**,直接作为 DeepSeek token 使用
**可选请求头**`X-Ds2-Target-Account: <email_or_mobile>` — 指定使用某个托管账号。
### Admin 接口(`/admin/*`
| 端点 | 鉴权 |
| --- | --- |
| `POST /admin/login` | 无需鉴权 |
| `GET /admin/verify` | `Authorization: Bearer <jwt>`(仅 JWT |
| 其他 `/admin/*` | `Authorization: Bearer <jwt>``Authorization: Bearer <admin_key>`(直传管理密钥) |
---
## 路由总览
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/healthz` | 存活探针 |
| GET | `/readyz` | 就绪探针 |
| GET | `/v1/models` | OpenAI 模型列表 |
| POST | `/v1/chat/completions` | OpenAI 对话补全 |
| GET | `/anthropic/v1/models` | Claude 模型列表 |
| POST | `/anthropic/v1/messages` | Claude 消息接口 |
| POST | `/anthropic/v1/messages/count_tokens` | Claude token 计数 |
| POST | `/admin/login` | 管理登录 |
| GET | `/admin/verify` | 校验管理 JWT |
| GET | `/admin/vercel/config` | 读取 Vercel 预配置 |
| GET | `/admin/config` | 读取配置(脱敏) |
| POST | `/admin/config` | 更新配置 |
| POST | `/admin/keys` | 添加 API key |
| DELETE | `/admin/keys/{key}` | 删除 API key |
| GET | `/admin/accounts` | 分页账号列表 |
| POST | `/admin/accounts` | 添加账号 |
| DELETE | `/admin/accounts/{identifier}` | 删除账号 |
| GET | `/admin/queue/status` | 账号队列状态 |
| POST | `/admin/accounts/test` | 测试单个账号 |
| POST | `/admin/accounts/test-all` | 测试全部账号 |
| POST | `/admin/import` | 批量导入 keys/accounts |
| POST | `/admin/test` | 测试当前 API 可用性 |
| POST | `/admin/vercel/sync` | 同步配置到 Vercel |
| GET | `/admin/vercel/status` | Vercel 同步状态 |
| GET | `/admin/export` | 导出配置 JSON/Base64 |
| 方法 | 路径 | 鉴权 | 说明 |
| --- | --- | --- | --- |
| GET | `/healthz` | 无 | 存活探针 |
| GET | `/readyz` | 无 | 就绪探针 |
| GET | `/v1/models` | 无 | OpenAI 模型列表 |
| POST | `/v1/chat/completions` | 业务 | OpenAI 对话补全 |
| GET | `/anthropic/v1/models` | 无 | Claude 模型列表 |
| POST | `/anthropic/v1/messages` | 业务 | Claude 消息接口 |
| POST | `/anthropic/v1/messages/count_tokens` | 业务 | Claude token 计数 |
| POST | `/admin/login` | 无 | 管理登录 |
| GET | `/admin/verify` | JWT | 校验管理 JWT |
| GET | `/admin/vercel/config` | Admin | 读取 Vercel 预配置 |
| GET | `/admin/config` | Admin | 读取配置(脱敏) |
| POST | `/admin/config` | Admin | 更新配置 |
| POST | `/admin/keys` | Admin | 添加 API key |
| DELETE | `/admin/keys/{key}` | Admin | 删除 API key |
| GET | `/admin/accounts` | Admin | 分页账号列表 |
| POST | `/admin/accounts` | Admin | 添加账号 |
| DELETE | `/admin/accounts/{identifier}` | Admin | 删除账号 |
| GET | `/admin/queue/status` | Admin | 账号队列状态 |
| POST | `/admin/accounts/test` | Admin | 测试单个账号 |
| POST | `/admin/accounts/test-all` | Admin | 测试全部账号 |
| POST | `/admin/import` | Admin | 批量导入 keys/accounts |
| POST | `/admin/test` | Admin | 测试当前 API 可用性 |
| POST | `/admin/vercel/sync` | Admin | 同步配置到 Vercel |
| GET | `/admin/vercel/status` | Admin | Vercel 同步状态 |
| GET | `/admin/export` | Admin | 导出配置 JSON/Base64 |
---
## 健康检查
### `GET /healthz`
响应:
```json
{"status":"ok"}
{"status": "ok"}
```
### `GET /readyz`
响应:
```json
{"status":"ready"}
{"status": "ready"}
```
---
## OpenAI 兼容接口
### `GET /v1/models`
无需鉴权。
无需鉴权。返回当前支持的模型列表。
响应示例:
**响应示例**
```json
{
@@ -95,24 +129,24 @@ Admin 接口:
### `POST /v1/chat/completions`
请求头示例
**请求头**
```http
Authorization: Bearer your-api-key
Content-Type: application/json
```
请求体核心字段
**请求体**
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `model` | string | | `deepseek-chat` / `deepseek-reasoner` / `deepseek-chat-search` / `deepseek-reasoner-search` |
| `messages` | array | | OpenAI 风格消息数组 |
| `stream` | boolean | | 默认 `false` |
| `tools` | array | | Function Calling 定义 |
| `temperature` 等 | any | | 兼容透传字段(最终是否生效由上游决定) |
| `model` | string | | `deepseek-chat` / `deepseek-reasoner` / `deepseek-chat-search` / `deepseek-reasoner-search` |
| `messages` | array | | OpenAI 风格消息数组 |
| `stream` | boolean | | 默认 `false` |
| `tools` | array | | Function Calling 定义 |
| `temperature` 等 | any | | 兼容透传字段(最终效由上游决定) |
非流式响应示例:
#### 非流式响应
```json
{
@@ -142,17 +176,10 @@ Content-Type: application/json
}
```
### OpenAI 流式(`stream=true`
#### 流式响应`stream=true`
SSE 格式:每段为 `data: <json>\n\n`,结束为 `data: [DONE]`
- 首次 delta 可能包含 `role: assistant`
- reasoner 模型会输出 `delta.reasoning_content`
- 普通文本输出 `delta.content`
- 最后一段包含 `finish_reason`,并附带 usage
示例:
```text
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant"},"index":0}]}
@@ -165,15 +192,18 @@ data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{},"index
data: [DONE]
```
### Tool Calls重点
**字段说明**
请求中带 `tools` 时,服务端会注入工具提示并尝试解析模型输出。
- 首个 delta 包含 `role: assistant`
- `deepseek-reasoner` / `deepseek-reasoner-search` 模型输出 `delta.reasoning_content`
- 普通文本输出 `delta.content`
- 最后一段包含 `finish_reason``usage`
- 非流式:若识别到工具调用,返回 `message.tool_calls`,并设置 `finish_reason=tool_calls``message.content=null`
- 流式:为防止原始 toolcall JSON 泄漏,正文会先缓冲;若识别到工具调用,仅输出结构化 `delta.tool_calls`
- 流式 `delta.tool_calls` 兼容严格客户端:每个 tool call 对象都带 `index`(从 `0` 开始)
#### Tool Calls
工具调用响应示例
当请求中含 `tools`DS2API 做防泄漏处理
**非流式**:识别到工具调用时,返回 `message.tool_calls`,设置 `finish_reason=tool_calls``message.content=null`
```json
{
@@ -200,13 +230,17 @@ data: [DONE]
}
```
**流式**:先缓冲正文片段。识别到工具调用 → 仅输出结构化 `delta.tool_calls`(每个 tool call 带 `index`);否则一次性输出普通文本。
---
## Claude 兼容接口
### `GET /anthropic/v1/models`
无需鉴权。
响应示例:
**响应示例**
```json
{
@@ -221,7 +255,7 @@ data: [DONE]
### `POST /anthropic/v1/messages`
请求头可用
**请求头**
```http
x-api-key: your-api-key
@@ -229,18 +263,18 @@ Content-Type: application/json
anthropic-version: 2023-06-01
```
请求体核心字段
**请求体**
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `model` | string | | `claude-sonnet-4-20250514` / `-fast` / `-slow` |
| `messages` | array | | Claude 风格消息数组 |
| `max_tokens` | number | | 当前实现不会硬性截断上游输出 |
| `stream` | boolean | | 默认 `false` |
| `system` | string | | 可选系统提示 |
| `tools` | array | | Claude tool 定义 |
| `model` | string | | `claude-sonnet-4-20250514` / `-fast` / `-slow` |
| `messages` | array | | Claude 风格消息数组 |
| `max_tokens` | number | | 当前实现不会硬性截断上游输出 |
| `stream` | boolean | | 默认 `false` |
| `system` | string | | 可选系统提示 |
| `tools` | array | | Claude tool 定义 |
非流式响应示例:
#### 非流式响应
```json
{
@@ -260,13 +294,11 @@ anthropic-version: 2023-06-01
}
```
若识别到工具调用,`stop_reason=tool_use`并在 `content` 中返回 `tool_use` block。
若识别到工具调用,`stop_reason=tool_use``content` 中返回 `tool_use` block。
### Claude 流式(`stream=true`
#### 流式响应`stream=true`
返回 SSE包含 `event:` + `data:` 双行JSON 中保留 `type` 字段。
示例:
SSE 使用 `event:` + `data:` 双行格式,JSON 中保留 `type` 字段。
```text
event: message_start
@@ -291,15 +323,15 @@ event: message_stop
data: {"type":"message_stop"}
```
说明
**说明**
- 开启思维模型会输出 `thinking_delta`
- 当前不会输出 `signature_delta`(上游 DeepSeek 未提供可验证签名)
- `tools` 场景优先避免泄露原始工具 JSON不强制发送 `input_json_delta`
- 思维模型`-slow`会输出 `thinking_delta`
- 不会输出 `signature_delta`(上游 DeepSeek 未提供可验证签名)
- `tools` 场景优先避免泄露原始工具 JSON不强制发送 `input_json_delta`
### `POST /anthropic/v1/messages/count_tokens`
请求示例
**请求**
```json
{
@@ -310,7 +342,7 @@ data: {"type":"message_stop"}
}
```
响应示例
**响应**
```json
{
@@ -318,11 +350,15 @@ data: {"type":"message_stop"}
}
```
---
## Admin 接口
### `POST /admin/login`
请求:
无需鉴权。
**请求**
```json
{
@@ -331,9 +367,9 @@ data: {"type":"message_stop"}
}
```
说明:`expire_hours` 可省略,默认 24
`expire_hours` 可省略,默认 `24`
响应
**响应**
```json
{
@@ -345,9 +381,9 @@ data: {"type":"message_stop"}
### `GET /admin/verify`
请求头`Authorization: Bearer <jwt>`
需要 JWT`Authorization: Bearer <jwt>`
响应
**响应**
```json
{
@@ -359,7 +395,7 @@ data: {"type":"message_stop"}
### `GET /admin/vercel/config`
返回是否存在 Vercel 预配置
返回 Vercel 预配置状态。
```json
{
@@ -371,7 +407,7 @@ data: {"type":"message_stop"}
### `GET /admin/config`
返回脱敏配置
返回脱敏后的配置
```json
{
@@ -396,7 +432,7 @@ data: {"type":"message_stop"}
可更新 `keys``accounts``claude_mapping`
请求示例
**请求**
```json
{
@@ -413,34 +449,26 @@ data: {"type":"message_stop"}
### `POST /admin/keys`
请求:
```json
{"key":"new-api-key"}
{"key": "new-api-key"}
```
响应:
```json
{"success":true,"total_keys":3}
```
**响应**`{"success": true, "total_keys": 3}`
### `DELETE /admin/keys/{key}`
响应:
```json
{"success":true,"total_keys":2}
```
**响应**`{"success": true, "total_keys": 2}`
### `GET /admin/accounts`
查询参数:
**查询参数**
- `page`(默认 1
- `page_size`(默认 10最大 100
| 参数 | 默认 | 范围 |
| --- | --- | --- |
| `page` | `1` | ≥ 1 |
| `page_size` | `10` | 1100 |
响应
**响应**
```json
{
@@ -462,32 +490,20 @@ data: {"type":"message_stop"}
### `POST /admin/accounts`
请求示例:
```json
{"email":"user@example.com","password":"pwd"}
{"email": "user@example.com", "password": "pwd"}
```
响应:
```json
{"success":true,"total_accounts":6}
```
**响应**`{"success": true, "total_accounts": 6}`
### `DELETE /admin/accounts/{identifier}`
`identifier` 为 email 或 mobile。
响应:
```json
{"success":true,"total_accounts":5}
```
**响应**`{"success": true, "total_accounts": 5}`
### `GET /admin/queue/status`
响应:
```json
{
"available": 3,
@@ -500,22 +516,23 @@ data: {"type":"message_stop"}
}
```
字段说明:
- `max_inflight_per_account`:每个账号允许的并发 in-flight 请求上限(默认 `2`,可由环境变量覆盖)
- `recommended_concurrency`:建议客户端并发值,按 `账号数量 × max_inflight_per_account` 动态计算
| 字段 | 说明 |
| --- | --- |
| `available` | 当前可用账号数 |
| `in_use` | 当前使用中的账号数 |
| `total` | 总账号数 |
| `max_inflight_per_account` | 每账号并发上限 |
| `recommended_concurrency` | 建议并发值(`total × max_inflight_per_account` |
### `POST /admin/accounts/test`
请求字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `identifier` | | email 或 mobile |
| `model` | | 默认 `deepseek-chat` |
| `message` | | 空字符串时仅测试会话 |
| `identifier` | | email 或 mobile |
| `model` | | 默认 `deepseek-chat` |
| `message` | | 空字符串时仅测试会话创建 |
响应示例
**响应**
```json
{
@@ -529,33 +546,33 @@ data: {"type":"message_stop"}
### `POST /admin/accounts/test-all`
请求可选:`model`
响应示例:
可选请求字段`model`
```json
{
"total": 5,
"success": 4,
"failed": 1,
"results": []
"results": [...]
}
```
### `POST /admin/import`
请求支持同时导入 `keys``accounts`
批量导入 keys 与 accounts
**请求**
```json
{
"keys": ["k1", "k2"],
"accounts": [
{"email":"user@example.com","password":"pwd","token":""}
{"email": "user@example.com", "password": "pwd", "token": ""}
]
}
```
响应
**响应**
```json
{
@@ -567,35 +584,35 @@ data: {"type":"message_stop"}
### `POST /admin/test`
请求字段(均可选):
测试当前 API 可用性(通过自身接口调用)。
- `model`(默认 `deepseek-chat`
- `message`(默认 `你好`
- `api_key`(默认使用配置中第一个 key
| 字段 | 必填 | 默认值 |
| --- | --- | --- |
| `model` | ❌ | `deepseek-chat` |
| `message` | ❌ | `你好` |
| `api_key` | ❌ | 配置中第一个 key |
响应示例
**响应**
```json
{
"success": true,
"status_code": 200,
"response": {"id":"..."}
"response": {"id": "..."}
}
```
### `POST /admin/vercel/sync`
请求字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `vercel_token` | | 空或 `__USE_PRECONFIG__` 则读环境变量 |
| `project_id` | | 空则读 `VERCEL_PROJECT_ID` |
| `team_id` | | 空则读 `VERCEL_TEAM_ID` |
| `auto_validate` | | 默认 `true` |
| `save_credentials` | | 默认 `true` |
| `vercel_token` | | 空或 `__USE_PRECONFIG__` 则读环境变量 |
| `project_id` | | 空则读 `VERCEL_PROJECT_ID` |
| `team_id` | | 空则读 `VERCEL_TEAM_ID` |
| `auto_validate` | | 默认 `true` |
| `save_credentials` | | 默认 `true` |
成功响应示例
**成功响应**
```json
{
@@ -606,7 +623,7 @@ data: {"type":"message_stop"}
}
```
或:
需要手动部署
```json
{
@@ -619,8 +636,6 @@ data: {"type":"message_stop"}
### `GET /admin/vercel/status`
响应:
```json
{
"synced": true,
@@ -631,8 +646,6 @@ data: {"type":"message_stop"}
### `GET /admin/export`
响应:
```json
{
"json": "{...}",
@@ -640,15 +653,29 @@ data: {"type":"message_stop"}
}
```
---
## 错误响应格式
不同模块错误格式不完全一致(按当前实现)
不同模块错误格式略有差异
- OpenAI 接口:`{"error":{"message":"...","type":"..."}}`
- Claude 接口常见:`{"error":{"type":"...","message":"..."}}`
- Admin 接口常见:`{"detail":"..."}`
| 模块 | 格式 |
| --- | --- |
| OpenAI 接口 | `{"error": {"message": "...", "type": "..."}}` |
| Claude 接口 | `{"error": {"type": "...", "message": "..."}}` |
| Admin 接口 | `{"detail": "..."}` |
建议客户端至少处理:HTTP 状态码 + `error` / `detail` 字段。
建议客户端处理逻辑:检查 HTTP 状态码 + 解析 `error` `detail` 字段。
**常见状态码**
| 状态码 | 说明 |
| --- | --- |
| `401` | 鉴权失败key/token 无效,或 Admin JWT 过期) |
| `429` | 请求过多(超出并发上限 + 等待队列) |
| `503` | 模型不可用或上游服务异常 |
---
## cURL 示例
@@ -678,7 +705,48 @@ curl http://localhost:5001/v1/chat/completions \
}'
```
### Claude
### OpenAI 带搜索
```bash
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat-search",
"messages": [{"role": "user", "content": "今天的新闻"}],
"stream": true
}'
```
### OpenAI Tool Calling
```bash
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "北京今天天气怎么样?"}],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}
}
]
}'
```
### Claude 非流式
```bash
curl http://localhost:5001/anthropic/v1/messages \
@@ -691,3 +759,39 @@ curl http://localhost:5001/anthropic/v1/messages \
"messages": [{"role": "user", "content": "你好"}]
}'
```
### Claude 流式
```bash
curl http://localhost:5001/anthropic/v1/messages \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-sonnet-4-20250514-slow",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "解释相对论"}],
"stream": true
}'
```
### Admin 登录
```bash
curl http://localhost:5001/admin/login \
-H "Content-Type: application/json" \
-d '{"admin_key": "admin"}'
```
### 指定账号请求
```bash
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "X-Ds2-Target-Account: user@example.com" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "你好"}]
}'
```

View File

@@ -2,11 +2,17 @@
Language: [中文](CONTRIBUTING.md) | [English](CONTRIBUTING.en.md)
Thanks for contributing to DS2API.
Thanks for your interest in contributing to DS2API!
## Development Setup
### Backend (Go)
### Prerequisites
- Go 1.24+
- Node.js 20+ (for WebUI development)
- npm (bundled with Node.js)
### Backend Development
```bash
# 1. Clone
@@ -15,56 +21,115 @@ cd ds2api
# 2. Configure
cp config.example.json config.json
# Edit config.json
# Edit config.json with test accounts
# 3. Run backend
go run ./cmd/ds2api
# Default: http://localhost:5001
```
### Frontend (WebUI)
### Frontend Development (WebUI)
```bash
# 1. Navigate to WebUI directory
cd webui
# 2. Install dependencies
npm install
# 3. Start dev server (hot reload)
npm run dev
# Default: http://localhost:5173, auto-proxies API to backend
```
WebUI locales are in `webui/src/locales/`.
WebUI tech stack:
- React + Vite
- Tailwind CSS
- Bilingual language packs: `webui/src/locales/zh.json` / `en.json`
### Docker Development
```bash
docker-compose -f docker-compose.dev.yml up
```
## Code Standards
- **Go**: run `gofmt` and make sure `go test ./...` passes before committing
- **JavaScript/React**: follow existing project style (functional components)
- **Commit messages**: use semantic prefixes (`feat:`, `fix:`, `docs:`)
| Language | Standards |
| --- | --- |
| **Go** | Run `gofmt` and ensure `go test ./...` passes before committing |
| **JavaScript/React** | Follow existing project style (functional components) |
| **Commit messages** | Use semantic prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `style:`, `perf:`, `chore:` |
## Submitting a PR
1. Fork the repo
2. Create a branch (e.g. `feature/xxx`)
2. Create a branch (e.g. `feature/xxx` or `fix/xxx`)
3. Commit changes
4. Push your branch
5. Open a Pull Request
> 💡 If you modify files under `webui/`, no manual build is needed — CI handles it automatically.
## Build WebUI
Manually build WebUI to `static/admin/`:
```bash
./scripts/build-webui.sh
```
## Running Tests
```bash
# Go unit tests
go test ./...
# End-to-end live tests (real accounts)
./scripts/testsuite/run-live.sh
```
## Project Structure
```text
ds2api/
├── cmd/ds2api/ # Local/container entrypoint
├── api/index.go # Vercel serverless entrypoint
├── internal/ # Go backend implementation
├── webui/ # React WebUI source
├── static/admin/ # WebUI build output
├── Dockerfile
├── docker-compose.yml
── vercel.json
├── cmd/
├── ds2api/ # Local/container entrypoint
│ └── ds2api-tests/ # End-to-end testsuite entrypoint
├── api/
│ ├── index.go # Vercel Serverless Go entry
│ ├── chat-stream.js # Vercel Node.js stream relay
│ └── helpers/ # Node.js helper modules
── internal/
│ ├── account/ # Account pool and concurrency queue
│ ├── adapter/
│ │ ├── openai/ # OpenAI adapter
│ │ └── claude/ # Claude adapter
│ ├── admin/ # Admin API handlers
│ ├── auth/ # Auth and JWT
│ ├── config/ # Config loading
│ ├── deepseek/ # DeepSeek client, PoW WASM
│ ├── server/ # HTTP routing (chi router)
│ ├── sse/ # SSE parsing utilities
│ ├── testsuite/ # Testsuite core logic
│ ├── util/ # Common utilities
│ └── webui/ # WebUI static hosting
├── webui/ # React WebUI source
│ └── src/
│ ├── components/ # Components
│ └── locales/ # Language packs
├── scripts/ # Build and test scripts
├── static/admin/ # WebUI build output (not committed)
├── Dockerfile # Multi-stage build
├── docker-compose.yml # Production
├── docker-compose.dev.yml # Development
└── vercel.json # Vercel config
```
## Reporting Issues
Please use [GitHub Issues](https://github.com/CJackHwang/ds2api/issues) with reproducible steps and logs.
Please use [GitHub Issues](https://github.com/CJackHwang/ds2api/issues) and include:
- Steps to reproduce
- Relevant log output
- Environment info (OS, Go version, deployment method)

View File

@@ -2,11 +2,17 @@
语言 / Language: [中文](CONTRIBUTING.md) | [English](CONTRIBUTING.en.md)
感谢你对 DS2API 的贡献
感谢你对 DS2API 的关注与贡献
## 开发环境设置
### 后端Go
### 前置要求
- Go 1.24+
- Node.js 20+WebUI 开发时)
- npm随 Node.js 提供)
### 后端开发
```bash
# 1. 克隆仓库
@@ -15,56 +21,115 @@ cd ds2api
# 2. 配置
cp config.example.json config.json
# 编辑 config.json
# 编辑 config.json,填入测试账号
# 3. 启动后端
go run ./cmd/ds2api
# 默认监听 http://localhost:5001
```
### 前端WebUI
### 前端开发WebUI
```bash
# 1. 进入 WebUI 目录
cd webui
# 2. 安装依赖
npm install
# 3. 启动开发服务器(热更新)
npm run dev
# 默认监听 http://localhost:5173自动代理 API 到后端
```
WebUI 语言包位于 `webui/src/locales/`
WebUI 技术栈:
- React + Vite
- Tailwind CSS
- 中英文语言包:`webui/src/locales/zh.json` / `en.json`
### Docker 开发环境
```bash
docker-compose -f docker-compose.dev.yml up
```
## 代码规范
- **Go**: 提交前运行 `gofmt`,并确保 `go test ./...` 通过
- **JavaScript/React**: 保持现有代码风格(函数组件)
- **提交信息**: 使用语义化前缀(`feat:`, `fix:`, `docs:`
| 语言 | 规范 |
| --- | --- |
| **Go** | 提交前运行 `gofmt`,确保 `go test ./...` 通过 |
| **JavaScript/React** | 保持现有代码风格(函数组件) |
| **提交信息** | 使用语义化前缀:`feat:``fix:``docs:``refactor:``style:``perf:``chore:` |
## 提交 PR
1. Fork 仓库
2. 创建分支(如 `feature/xxx`
2. 创建分支(如 `feature/xxx``fix/xxx`
3. 提交更改
4. 推送分支
5. 发起 Pull Request
> 💡 如果修改了 `webui/` 目录下的文件无需手动构建——CI 会自动处理。
## WebUI 构建
手动构建 WebUI 到 `static/admin/`
```bash
./scripts/build-webui.sh
```
## 运行测试
```bash
# Go 单元测试
go test ./...
# 端到端全链路测试(真实账号)
./scripts/testsuite/run-live.sh
```
## 项目结构
```text
ds2api/
├── cmd/ds2api/ # 本地/容器启动入口
├── api/index.go # Vercel Serverless 入口
├── internal/ # Go 后端核心实现
├── webui/ # React WebUI 源码
├── static/admin/ # WebUI 构建产物
├── Dockerfile
├── docker-compose.yml
── vercel.json
├── cmd/
├── ds2api/ # 本地/容器启动入口
│ └── ds2api-tests/ # 端到端测试集入口
├── api/
│ ├── index.go # Vercel Serverless Go 入口
│ ├── chat-stream.js # Vercel Node.js 流式转发
│ └── helpers/ # Node.js 辅助模块
── internal/
│ ├── account/ # 账号池与并发队列
│ ├── adapter/
│ │ ├── openai/ # OpenAI 兼容适配器
│ │ └── claude/ # Claude 兼容适配器
│ ├── admin/ # Admin API handlers
│ ├── auth/ # 鉴权与 JWT
│ ├── config/ # 配置加载
│ ├── deepseek/ # DeepSeek 客户端、PoW WASM
│ ├── server/ # HTTP 路由chi router
│ ├── sse/ # SSE 解析工具
│ ├── testsuite/ # 测试集核心逻辑
│ ├── util/ # 通用工具
│ └── webui/ # WebUI 静态托管
├── webui/ # React WebUI 源码
│ └── src/
│ ├── components/ # 组件
│ └── locales/ # 语言包
├── scripts/ # 构建与测试脚本
├── static/admin/ # WebUI 构建产物(不提交)
├── Dockerfile # 多阶段构建
├── docker-compose.yml # 生产环境
├── docker-compose.dev.yml # 开发环境
└── vercel.json # Vercel 配置
```
## 问题反馈
请使用 [GitHub Issues](https://github.com/CJackHwang/ds2api/issues) 并附复现步骤与日志。
请使用 [GitHub Issues](https://github.com/CJackHwang/ds2api/issues) 并附上:
- 复现步骤
- 相关日志输出
- 运行环境信息OS、Go 版本、部署方式)

View File

@@ -1,174 +1,328 @@
# DS2API Deployment Guide (Go)
# DS2API Deployment Guide
Language: [中文](DEPLOY.md) | [English](DEPLOY.en.md)
This guide is aligned with the current Go codebase.
This guide covers all deployment methods for the current Go-based codebase.
## Deployment Modes
---
- Local run: `go run ./cmd/ds2api`
- Docker: `docker-compose up -d`
- Vercel: serverless entry at `api/index.go`
- Linux service mode: systemd
## Table of Contents
- [Prerequisites](#0-prerequisites)
- [1. Local Run](#1-local-run)
- [2. Docker Deployment](#2-docker-deployment)
- [3. Vercel Deployment](#3-vercel-deployment)
- [4. Download Release Binaries](#4-download-release-binaries)
- [5. Reverse Proxy (Nginx)](#5-reverse-proxy-nginx)
- [6. Linux systemd Service](#6-linux-systemd-service)
- [7. Post-Deploy Checks](#7-post-deploy-checks)
- [8. Pre-Release Local Regression](#8-pre-release-local-regression)
---
## 0. Prerequisites
- Go 1.24+
- Node.js 20+ (only if you need to build WebUI locally)
- `config.json` or `DS2API_CONFIG_JSON`
| Dependency | Minimum Version | Notes |
| --- | --- | --- |
| Go | 1.24+ | Build backend |
| Node.js | 20+ | Only needed to build WebUI locally |
| npm | Bundled with Node.js | Install WebUI dependencies |
Config source (choose one):
- **File**: `config.json` (recommended for local/Docker)
- **Environment variable**: `DS2API_CONFIG_JSON` (recommended for Vercel; supports raw JSON or Base64)
---
## 1. Local Run
### 1.1 Basic Steps
```bash
# Clone
git clone https://github.com/CJackHwang/ds2api.git
cd ds2api
# Copy and edit config
cp config.example.json config.json
# edit config.json
# Open config.json and fill in:
# - keys: your API access keys
# - accounts: DeepSeek accounts (email or mobile + password)
# Start
go run ./cmd/ds2api
```
Default port is `5001` (override with `PORT`).
Default address: `http://0.0.0.0:5001` (override with `PORT`).
Build WebUI if `/admin` reports missing assets:
### 1.2 WebUI Build
On first local startup, if `static/admin/` is missing, DS2API will automatically attempt to build the WebUI (requires Node.js/npm).
Manual build:
```bash
./scripts/build-webui.sh
# Or rely on startup auto-build (enabled locally by default)
# DS2API_AUTO_BUILD_WEBUI=true go run ./cmd/ds2api
```
Or step by step:
```bash
cd webui
npm install
npm run build
# Output goes to static/admin/
```
Control auto-build via environment variable:
```bash
# Disable auto-build
DS2API_AUTO_BUILD_WEBUI=false go run ./cmd/ds2api
# Force enable auto-build
DS2API_AUTO_BUILD_WEBUI=true go run ./cmd/ds2api
```
### 1.3 Compile to Binary
```bash
go build -o ds2api ./cmd/ds2api
./ds2api
```
---
## 2. Docker Deployment
```bash
cp .env.example .env
# edit .env
### 2.1 Basic Steps
```bash
# Copy and edit environment
cp .env.example .env
# Edit .env, at minimum set:
# DS2API_ADMIN_KEY=your-admin-key
# DS2API_CONFIG_JSON={"keys":[...],"accounts":[...]}
# Start
docker-compose up -d
# View logs
docker-compose logs -f
```
Rebuild after updates:
### 2.2 Update
```bash
docker-compose up -d --build
```
Notes:
### 2.3 Docker Architecture
- `Dockerfile` uses multi-stage build (WebUI + Go binary)
- Container entry command is `/usr/local/bin/ds2api`
The `Dockerfile` uses a three-stage build:
1. **WebUI build stage**: `node:20` image, runs `npm ci && npm run build`
2. **Go build stage**: `golang:1.24` image, compiles the binary
3. **Runtime stage**: `debian:bookworm-slim` minimal image
Container entry command: `/usr/local/bin/ds2api`, default exposed port: `5001`.
### 2.4 Development Mode
```bash
docker-compose -f docker-compose.dev.yml up
```
Development features:
- Source code mounted (live changes)
- `LOG_LEVEL=DEBUG`
- No auto-restart
### 2.5 Health Check
Docker Compose includes a built-in health check:
```yaml
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:5001/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
```
---
## 3. Vercel Deployment
- Serverless entry: `api/index.go`
- Rewrites and cache headers: `vercel.json`
- Build stage runs `npm ci --prefix webui && npm run build --prefix webui` automatically
- `vercel.json` routes `/admin/assets/*` and the `/admin` page to static output, while `/admin/*` APIs still go to `api/index`
- To mitigate Go Runtime streaming buffering, `/v1/chat/completions` on Vercel is routed to `api/chat-stream.js` (Node Runtime)
- `api/chat-stream.js` automatically falls back to the Go entry for non-stream requests or requests with `tools` (internal `__go=1`)
- `api/chat-stream.js` is data-path only (stream relay + SSE conversion); auth/account/session/PoW preparation still comes from an internal Go prepare endpoint (enabled on Vercel only)
- Go prepare creates a stream lease and Node releases it when streaming ends, keeping account occupancy semantics aligned with native Go streaming
- `vercel.json` sets `maxDuration: 300` for both `api/chat-stream.js` and `api/index.go` (subject to your Vercel plan limits)
### 3.1 Steps
Minimum environment variables:
1. **Fork** the repo to your GitHub account
2. **Import** the project on Vercel
3. **Set environment variables** (at minimum):
- `DS2API_ADMIN_KEY`
- `DS2API_CONFIG_JSON` (raw JSON or Base64)
| Variable | Description |
| --- | --- |
| `DS2API_ADMIN_KEY` | Admin key (required) |
| `DS2API_CONFIG_JSON` | Config content, raw JSON or Base64 (required) |
Optional:
4. **Deploy**
- `VERCEL_TOKEN`
- `VERCEL_PROJECT_ID`
- `VERCEL_TEAM_ID`
- `DS2API_ACCOUNT_MAX_INFLIGHT` (per-account inflight limit, default `2`)
- `DS2API_ACCOUNT_CONCURRENCY` (alias of the same setting)
- `DS2API_ACCOUNT_MAX_QUEUE` (waiting queue limit, default=`recommended_concurrency`)
- `DS2API_ACCOUNT_QUEUE_SIZE` (alias of the same setting)
- `DS2API_VERCEL_INTERNAL_SECRET` (optional internal auth secret for Vercel hybrid streaming path; falls back to `DS2API_ADMIN_KEY` when unset)
- `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` (optional stream lease TTL in seconds, default `900`)
### 3.2 Optional Environment Variables
Recommended concurrency is computed dynamically as `account_count * per_account_inflight_limit` (default is `account_count * 2`).
When inflight slots are full, requests are queued first; with default queue size, 429 typically starts around `account_count * 4`.
| Variable | Description | Default |
| --- | --- | --- |
| `DS2API_ACCOUNT_MAX_INFLIGHT` | Per-account inflight limit | `2` |
| `DS2API_ACCOUNT_CONCURRENCY` | Alias (legacy compat) | — |
| `DS2API_ACCOUNT_MAX_QUEUE` | Waiting queue limit | `recommended_concurrency` |
| `DS2API_ACCOUNT_QUEUE_SIZE` | Alias (legacy compat) | — |
| `DS2API_VERCEL_INTERNAL_SECRET` | Hybrid streaming internal auth | Falls back to `DS2API_ADMIN_KEY` |
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | Stream lease TTL | `900` |
| `VERCEL_TOKEN` | Vercel sync token | — |
| `VERCEL_PROJECT_ID` | Vercel project ID | — |
| `VERCEL_TEAM_ID` | Vercel team ID | — |
Notes:
- `static/admin` build output is not committed
- Vercel/Docker generate WebUI assets during build
After deploy, verify:
- `/healthz`
- `/v1/models`
- `/admin`
## 3.1 GitHub Release Automation
This repo includes `.github/workflows/release-artifacts.yml`:
- Triggers only on Release `published`
- Does not run on `push`
- Builds Linux/macOS/Windows archives and uploads them to Release Assets
- Generates `sha256sums.txt` for integrity checks
## 3.2 Vercel Build Troubleshooting
If you see an error like:
### 3.3 Vercel Architecture
```text
Error: Command failed: go build -ldflags -s -w -o .../bootstrap .../main__vc__go__.go
Request ──────┐
vercel.json routing
┌─────┴─────┐
│ │
▼ ▼
api/index.go api/chat-stream.js
(Go Runtime) (Node Runtime)
```
it is usually caused by invalid Go build flag settings in Vercel
(`-ldflags` not passed as a single argument).
- **Go entry**: `api/index.go` (Serverless Go)
- **Stream entry**: `api/chat-stream.js` (Node Runtime for real-time SSE)
- **Routing**: `vercel.json`
- **Build command**: `npm ci --prefix webui && npm run build --prefix webui` (automatic)
How to fix:
#### Streaming Pipeline
1. Open Vercel Project Settings -> Build and Development Settings
2. Clear custom Go Build Flags / Build Command (recommended)
3. If ldflags must be used, set `-ldflags=\"-s -w\"` so it is passed as one argument
4. Ensure `go.mod` uses a supported version (this repo uses `go 1.24`)
5. Redeploy (preferably with cache cleared)
Vercel Go Runtime applies platform-level response buffering, so this project uses a hybrid "**Go prepare + Node stream**" path on Vercel:
Another common root cause (Go monorepo + `internal/`):
1. `api/chat-stream.js` receives `/v1/chat/completions` request
2. Node calls Go internal prepare endpoint (`?__stream_prepare=1`) for session ID, PoW, token
3. Go prepare creates a stream lease, locking the account
4. Node connects directly to DeepSeek upstream, relays SSE in real-time to client
5. After stream ends, Node calls Go release endpoint (`?__stream_release=1`) to free the account
> This adaptation is **Vercel-only**; local and Docker remain pure Go.
#### Non-Stream and Tool Call Fallback
- `api/chat-stream.js` automatically falls back to Go entry (`?__go=1`) for non-stream requests or requests with `tools`
- WebUI non-stream test calls `?__go=1` directly to avoid Node hop timeout on long requests
#### Function Duration
`vercel.json` sets `maxDuration: 300` for both `api/chat-stream.js` and `api/index.go` (subject to your Vercel plan limits).
### 3.4 Vercel Troubleshooting
#### Go Build Failure
```text
... use of internal package ds2api/internal/server not allowed
Error: Command failed: go build -ldflags -s -w -o .../bootstrap ...
```
This usually happens when the Vercel Go entrypoint imports `internal/...` directly.
This repo now avoids that by using a public bridge package: `api/index.go` -> `ds2api/app` -> `internal/server`.
**Cause**: Invalid Go build flag settings in Vercel (`-ldflags` not passed as a single argument).
If you see:
**Fix**:
1. Open Vercel Project Settings → Build and Development Settings
2. **Clear** custom Go Build Flags / Build Command (recommended)
3. If ldflags must be used, set `-ldflags="-s -w"` (ensure it's one argument)
4. Verify `go.mod` uses a supported version (currently `go 1.24`)
5. Redeploy (recommended: clear cache)
#### Internal Package Import Error
```text
use of internal package ds2api/internal/server not allowed
```
**Cause**: Vercel Go entrypoint directly imports `internal/...`.
**Fix**: This repo uses a public bridge package: `api/index.go``ds2api/app``internal/server`.
#### Output Directory Error
```text
No Output Directory named "public" found after the Build completed.
```
Vercel is validating frontend output against `public`. This repo builds WebUI into `static/admin`, and uses the parent directory `static` as Vercel output root.
`vercel.json` now explicitly sets:
**Fix**: This repo uses `static` as output directory (`"outputDirectory": "static"` in `vercel.json`). If you manually changed Output Directory in Project Settings, set it to `static` or clear it.
```json
"outputDirectory": "static"
#### Deployment Protection Blocking
If API responses return Vercel HTML `Authentication Required`:
- **Option A**: Disable Deployment Protection for that environment (recommended for public APIs)
- **Option B**: Add `x-vercel-protection-bypass` header to requests
- **Option C**: Set `VERCEL_AUTOMATION_BYPASS_SECRET` (or `DS2API_VERCEL_PROTECTION_BYPASS`) for internal Node→Go calls
### 3.5 Build Artifacts Not Committed
- `static/admin` directory is not in Git
- Vercel / Docker automatically generate WebUI assets during build
---
## 4. Download Release Binaries
Built-in GitHub Actions workflow: `.github/workflows/release-artifacts.yml`
- **Trigger**: only on Release `published` (no build on normal push)
- **Outputs**: multi-platform binary archives + `sha256sums.txt`
| Platform | Architecture | Format |
| --- | --- | --- |
| Linux | amd64, arm64 | `.tar.gz` |
| macOS | amd64, arm64 | `.tar.gz` |
| Windows | amd64 | `.zip` |
Each archive includes:
- `ds2api` executable (`ds2api.exe` on Windows)
- `static/admin/` (built WebUI assets)
- `sha3_wasm_bg.7b9ca65ddd.wasm`
- `config.example.json`, `.env.example`
- `README.MD`, `README.en.md`, `LICENSE`
### Usage
```bash
# 1. Download the archive for your platform
# 2. Extract
tar -xzf ds2api_v1.7.0_linux_amd64.tar.gz
cd ds2api_v1.7.0_linux_amd64
# 3. Configure
cp config.example.json config.json
# Edit config.json
# 4. Start
./ds2api
```
If you manually changed Output Directory in Project Settings, set it to `static` (or clear it and let repo config apply).
### Maintainer Release Flow
If API responses return Vercel HTML `Authentication Required` (instead of JSON), the request is blocked by Vercel Deployment Protection:
1. Create and publish a GitHub Release (with tag, e.g. `v1.7.0`)
2. Wait for the `Release Artifacts` workflow to complete
3. Download the matching archive from Release Assets
- Disable protection for that deployment/environment (recommended for public API use)
- Or send `x-vercel-protection-bypass` in requests
- If only internal Node->Go calls are blocked, set `VERCEL_AUTOMATION_BYPASS_SECRET` (or `DS2API_VERCEL_PROTECTION_BYPASS`)
---
Vercel streaming note (important):
## 5. Reverse Proxy (Nginx)
- Vercel Go Runtime applies platform-level buffering, so this repo uses a hybrid path on Vercel (`Go prepare + Node stream`) to restore real-time SSE behavior.
- This adaptation is Vercel-only; local and Docker remain pure Go.
## 4. Reverse Proxy (Nginx)
Disable buffering for SSE:
When deploying behind Nginx, **you must disable buffering** for SSE streaming to work:
```nginx
location / {
@@ -182,9 +336,50 @@ location / {
}
```
## 5. systemd Example (Linux)
For HTTPS, add SSL at the Nginx layer:
```nginx
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:5001;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
tcp_nodelay on;
}
}
```
---
## 6. Linux systemd Service
### 6.1 Installation
```bash
# Copy compiled binary and related files to target directory
sudo mkdir -p /opt/ds2api
sudo cp ds2api config.json sha3_wasm_bg.7b9ca65ddd.wasm /opt/ds2api/
sudo cp -r static/admin /opt/ds2api/static/admin
```
### 6.2 Create systemd Service File
```ini
# /etc/systemd/system/ds2api.service
[Unit]
Description=DS2API (Go)
After=network.target
@@ -194,7 +389,7 @@ Type=simple
WorkingDirectory=/opt/ds2api
Environment=PORT=5001
Environment=DS2API_CONFIG_PATH=/opt/ds2api/config.json
Environment=DS2API_ADMIN_KEY=admin
Environment=DS2API_ADMIN_KEY=your-admin-key-here
ExecStart=/opt/ds2api/ds2api
Restart=always
RestartSec=5
@@ -203,38 +398,72 @@ RestartSec=5
WantedBy=multi-user.target
```
Common commands:
### 6.3 Common Commands
```bash
# Reload service config
sudo systemctl daemon-reload
# Enable on boot
sudo systemctl enable ds2api
# Start
sudo systemctl start ds2api
# Check status
sudo systemctl status ds2api
# View logs
sudo journalctl -u ds2api -f
# Restart
sudo systemctl restart ds2api
# Stop
sudo systemctl stop ds2api
```
## 6. Post-Deploy Checks
---
## 7. Post-Deploy Checks
After deployment (any method), verify in order:
```bash
# 1. Liveness probe
curl -s http://127.0.0.1:5001/healthz
# Expected: {"status":"ok"}
# 2. Readiness probe
curl -s http://127.0.0.1:5001/readyz
# Expected: {"status":"ready"}
# 3. Model list
curl -s http://127.0.0.1:5001/v1/models
# Expected: {"object":"list","data":[...]}
# 4. Admin panel (if WebUI is built)
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5001/admin
# Expected: 200
# 5. Test API call
curl http://127.0.0.1:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"hello"}]}'
```
If admin UI is required:
---
```bash
curl -s http://127.0.0.1:5001/admin
```
## 8. Pre-Release Local Regression
## 7. Pre-release Local Regression (Recommended)
Run the full live testsuite before release:
Run the full live testsuite before release (real account tests):
```bash
./scripts/testsuite/run-live.sh
```
Optional flags:
With custom flags:
```bash
go run ./cmd/ds2api-tests \
@@ -247,7 +476,9 @@ go run ./cmd/ds2api-tests \
The testsuite automatically performs:
- preflight checks (syntax/build/unit tests)
- isolated config copy startup (no mutation to your original `config.json`)
- live scenario verification (OpenAI/Claude/Admin/concurrency/toolcall/streaming)
- full request/response artifact logging for debugging
- ✅ Preflight checks (syntax/build/unit tests)
- ✅ Isolated config copy startup (no mutation to your original `config.json`)
- ✅ Live scenario verification (OpenAI/Claude/Admin/concurrency/toolcall/streaming)
- ✅ Full request/response artifact logging for debugging
For detailed testsuite documentation, see [TESTING.md](TESTING.md).

475
DEPLOY.md
View File

@@ -1,172 +1,328 @@
# DS2API 部署指南Go
# DS2API 部署指南
语言 / Language: [中文](DEPLOY.md) | [English](DEPLOY.en.md)
本指南基于当前 Go 代码库。
本指南基于当前 Go 代码库,详细说明各种部署方式
## 部署方式
---
- 本地运行:`go run ./cmd/ds2api`
- Docker`docker-compose up -d`
- Vercel`api/index.go` serverless 入口
- Linux 服务化systemd
## 目录
- [前置要求](#0-前置要求)
- [一、本地运行](#一本地运行)
- [二、Docker 部署](#二docker-部署)
- [三、Vercel 部署](#三vercel-部署)
- [四、下载 Release 构建包](#四下载-release-构建包)
- [五、反向代理Nginx](#五反向代理nginx)
- [六、Linux systemd 服务化](#六linux-systemd-服务化)
- [七、部署后检查](#七部署后检查)
- [八、发布前进行本地回归](#八发布前进行本地回归)
---
## 0. 前置要求
- Go 1.24+
- Node.js 20+(仅在需要本地构建 WebUI 时)
- `config.json``DS2API_CONFIG_JSON`
| 依赖 | 最低版本 | 说明 |
| --- | --- | --- |
| Go | 1.24+ | 编译后端 |
| Node.js | 20+ | 仅在需要本地构建 WebUI 时 |
| npm | 随 Node.js 提供 | 安装 WebUI 依赖 |
## 1. 本地运行
配置来源(任选其一):
- **文件方式**`config.json`(推荐本地/Docker 使用)
- **环境变量方式**`DS2API_CONFIG_JSON`(推荐 Vercel 使用,支持 JSON 字符串或 Base64 编码)
---
## 一、本地运行
### 1.1 基本步骤
```bash
# 克隆仓库
git clone https://github.com/CJackHwang/ds2api.git
cd ds2api
# 复制并编辑配置
cp config.example.json config.json
# 编辑 config.json
# 使用你喜欢的编辑器打开 config.json,填入:
# - keys: 你的 API 访问密钥
# - accounts: DeepSeek 账号email 或 mobile + password
# 启动服务
go run ./cmd/ds2api
```
默认监听 `5001`,可通过 `PORT` 覆盖。
默认监听 `http://0.0.0.0:5001`,可通过 `PORT` 环境变量覆盖。
构建 WebUI可选仅当 `/admin` 缺少静态文件时):
### 1.2 WebUI 构建
本地首次启动时,若 `static/admin/` 不存在,服务会自动尝试构建 WebUI需要 Node.js/npm
你也可以手动构建:
```bash
./scripts/build-webui.sh
# 或依赖自动构建(默认本地开启)
# DS2API_AUTO_BUILD_WEBUI=true go run ./cmd/ds2api
```
## 2. Docker 部署
或手动执行:
```bash
cp .env.example .env
# 编辑 .env
cd webui
npm install
npm run build
# 产物输出到 static/admin/
```
通过环境变量控制自动构建行为:
```bash
# 强制关闭自动构建
DS2API_AUTO_BUILD_WEBUI=false go run ./cmd/ds2api
# 强制开启自动构建
DS2API_AUTO_BUILD_WEBUI=true go run ./cmd/ds2api
```
### 1.3 编译为二进制文件
```bash
go build -o ds2api ./cmd/ds2api
./ds2api
```
---
## 二、Docker 部署
### 2.1 基本步骤
```bash
# 复制并编辑环境变量
cp .env.example .env
# 编辑 .env至少设置
# DS2API_ADMIN_KEY=your-admin-key
# DS2API_CONFIG_JSON={"keys":[...],"accounts":[...]}
# 启动
docker-compose up -d
# 查看日志
docker-compose logs -f
```
更新镜像:
### 2.2 更新
```bash
docker-compose up -d --build
```
说明:
### 2.3 Docker 架构说明
- `Dockerfile` 使用阶段构建WebUI + Go 二进制)
- 容器内默认启动命令:`/usr/local/bin/ds2api`
`Dockerfile` 使用阶段构建
## 3. Vercel 部署
1. **WebUI 构建阶段**`node:20` 镜像,执行 `npm ci && npm run build`
2. **Go 构建阶段**`golang:1.24` 镜像,编译二进制文件
3. **运行阶段**`debian:bookworm-slim` 精简镜像
- serverless 入口:`api/index.go`
- 路由与缓存头:`vercel.json`
- 构建阶段会自动执行 `npm ci --prefix webui && npm run build --prefix webui`
- `vercel.json` 已将 `/admin/assets/*``/admin` 页面走静态产物,`/admin/*` API 仍走 `api/index`
- 为缓解 Go Runtime 的流式缓冲,`/v1/chat/completions` 在 Vercel 上会优先走 `api/chat-stream.js`Node Runtime
- `api/chat-stream.js` 对非流式请求或 `tools` 请求会自动回退到 Go 入口(内部 `__go=1`
- `api/chat-stream.js` 仅负责流式数据转发与 SSE 转换鉴权、账号选择、会话创建、PoW 计算仍由 Go 内部 prepare 接口完成(仅 Vercel 启用)
- Go prepare 会创建流式 leaseNode 在流结束后回调 release账号占用语义与 Go 原生流式保持一致
- `vercel.json` 已将 `api/chat-stream.js``api/index.go``maxDuration` 设为 `300`(受套餐上限约束)
容器内启动命令:`/usr/local/bin/ds2api`,默认暴露端口 `5001`
至少配置环境变量:
### 2.4 开发环境
- `DS2API_ADMIN_KEY`
- `DS2API_CONFIG_JSON`JSON 或 Base64
可选:
- `VERCEL_TOKEN`
- `VERCEL_PROJECT_ID`
- `VERCEL_TEAM_ID`
- `DS2API_ACCOUNT_MAX_INFLIGHT`(每账号并发上限,默认 `2`
- `DS2API_ACCOUNT_CONCURRENCY`(同上别名)
- `DS2API_ACCOUNT_MAX_QUEUE`(等待队列上限,默认=`recommended_concurrency`
- `DS2API_ACCOUNT_QUEUE_SIZE`(同上别名)
- `DS2API_VERCEL_INTERNAL_SECRET`可选Vercel 混合流式链路内部鉴权;未设置时回退使用 `DS2API_ADMIN_KEY`
- `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS`(可选,流式 lease 过期秒数,默认 `900`
并发建议值会动态按 `账号数量 × 每账号并发上限` 计算(默认即 `账号数量 × 2`)。
当 in-flight 满时,请求先进入等待队列;默认队列上限等于建议并发值,因此默认 429 阈值约为 `账号数量 × 4`
说明:
- 仓库不提交 `static/admin` 构建产物
- Vercel / Docker 构建阶段自动生成 WebUI 静态文件
部署后建议先访问:
- `/healthz`
- `/v1/models`
- `/admin`
## 3.1 GitHub Release 自动构建
仓库包含 `.github/workflows/release-artifacts.yml`
- 仅在 Release `published` 时触发
- 不在 `push` 时触发
- 自动构建 Linux/macOS/Windows 二进制包并上传到 Release Assets
- 生成 `sha256sums.txt` 供校验
## 3.2 Vercel 常见报错排查
若看到类似报错:
```text
Error: Command failed: go build -ldflags -s -w -o .../bootstrap .../main__vc__go__.go
```bash
docker-compose -f docker-compose.dev.yml up
```
通常是 Vercel 项目里的 Go 构建参数配置不正确(`-ldflags` 没有作为一个整体字符串传递)。
开发模式特性:
- 源代码挂载(修改即生效)
- `LOG_LEVEL=DEBUG`
- 不自动重启
处理方式:
### 2.5 健康检查
1. 进入 Vercel Project Settings -> Build and Development Settings
2. 清空自定义 Go Build Flags / Build Command推荐
3. 若必须设置 ldflags使用 `-ldflags=\"-s -w\"`(保证它是一个参数)
Docker Compose 已配置内置健康检查:
```yaml
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:5001/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
```
---
## 三、Vercel 部署
### 3.1 部署步骤
1. **Fork 仓库**到你的 GitHub 账号
2. **在 Vercel 上导入项目**
3. **配置环境变量**(至少设置以下两项):
| 变量 | 说明 |
| --- | --- |
| `DS2API_ADMIN_KEY` | 管理密钥(必填) |
| `DS2API_CONFIG_JSON` | 配置内容JSON 字符串或 Base64 编码(必填) |
4. **部署**
### 3.2 可选环境变量
| 变量 | 说明 | 默认值 |
| --- | --- | --- |
| `DS2API_ACCOUNT_MAX_INFLIGHT` | 每账号并发上限 | `2` |
| `DS2API_ACCOUNT_CONCURRENCY` | 同上(兼容别名) | — |
| `DS2API_ACCOUNT_MAX_QUEUE` | 等待队列上限 | `recommended_concurrency` |
| `DS2API_ACCOUNT_QUEUE_SIZE` | 同上(兼容别名) | — |
| `DS2API_VERCEL_INTERNAL_SECRET` | 混合流式内部鉴权 | 回退用 `DS2API_ADMIN_KEY` |
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | 流式 lease TTL | `900` |
| `VERCEL_TOKEN` | Vercel 同步 token | — |
| `VERCEL_PROJECT_ID` | Vercel 项目 ID | — |
| `VERCEL_TEAM_ID` | Vercel 团队 ID | — |
### 3.3 Vercel 架构说明
```text
请求 ─────┐
vercel.json 路由规则
┌─────┴─────┐
│ │
▼ ▼
api/index.go api/chat-stream.js
(Go Runtime) (Node Runtime)
```
- **入口文件**`api/index.go`Serverless Go
- **流式入口**`api/chat-stream.js`Node Runtime保证实时 SSE
- **路由重写**`vercel.json`
- **构建命令**`npm ci --prefix webui && npm run build --prefix webui`(自动执行)
#### 流式处理链路
由于 Vercel Go Runtime 存在平台层响应缓冲,本项目在 Vercel 上采用"**Go prepare + Node stream**"的混合链路:
1. `api/chat-stream.js` 收到 `/v1/chat/completions` 请求
2. Node 调用 Go 内部 prepare 接口(`?__stream_prepare=1`),获取会话 ID、PoW、token 等
3. Go prepare 创建 stream lease锁定账号
4. Node 直连 DeepSeek 上游,实时流式转发 SSE 给客户端
5. 流结束后 Node 调用 Go release 接口(`?__stream_release=1`),释放账号
> 该适配**仅在 Vercel 环境生效**;本地与 Docker 仍走纯 Go 链路。
#### 非流式与 Tool Call 回退
- `api/chat-stream.js` 对非流式请求或带 `tools` 的请求会自动回退到 Go 入口(`?__go=1`
- WebUI 的"非流式测试"直接请求 `?__go=1`,避免 Node 中转造成长请求超时
#### 函数时长
`vercel.json` 已将 `api/chat-stream.js``api/index.go``maxDuration` 设为 `300`(受 Vercel 套餐上限约束)。
### 3.4 Vercel 常见报错排查
#### Go 构建失败
```text
Error: Command failed: go build -ldflags -s -w -o .../bootstrap ...
```
**原因**Vercel 项目的 Go 构建参数配置不正确(`-ldflags` 没有作为一个整体字符串传递)。
**解决**
1. 进入 Vercel Project Settings → Build and Development Settings
2. **清空**自定义 Go Build Flags / Build Command推荐
3. 若必须设置 ldflags使用 `-ldflags="-s -w"`(保证它是一个参数)
4. 确认仓库 `go.mod` 为受支持版本(当前为 `go 1.24`
5. 重新部署(建议 `Redeploy` 并清缓存
5. 重新部署(建议清缓存后 Redeploy
另一个常见根因Go 单仓 + `internal/`
#### Internal 包导入错误
```text
... use of internal package ds2api/internal/server not allowed
use of internal package ds2api/internal/server not allowed
```
这通常发生在 Vercel Go 入口文件直接 `import internal/...`
当前仓库已通过公开桥接包 `app` 解决:`api/index.go` -> `ds2api/app` -> `internal/server`
**原因**Vercel Go 入口文件直接 `import internal/...`
若看到类似报错:
**解决**:当前仓库已通过公开桥接包 `app` 解决:`api/index.go``ds2api/app``internal/server`
#### 输出目录错误
```text
No Output Directory named "public" found after the Build completed.
```
说明 Vercel 正在按 `public` 校验前端产物目录。当前仓库会将 WebUI 构建到 `static/admin`,并在 `vercel.json` 使用上级目录 `static` 作为输出根目录:
**解决**:当前仓库使用 `static` 作为输出目录(`vercel.json``"outputDirectory": "static"`)。若你在项目设置里手动改过 Output Directory请设为 `static` 或清空让仓库配置生效。
```json
"outputDirectory": "static"
#### 部署保护拦截
如果接口返回 Vercel HTML 页面 `Authentication Required`
- **方案 A**:关闭该部署/环境的 Deployment Protection推荐用于公开 API
- **方案 B**:请求中添加 `x-vercel-protection-bypass`
- **方案 C**:设置 `VERCEL_AUTOMATION_BYPASS_SECRET`(或 `DS2API_VERCEL_PROTECTION_BYPASS`),仅影响内部 Node→Go 调用
### 3.5 仓库不提交构建产物
- `static/admin` 目录不在 Git 中
- Vercel / Docker 构建阶段自动生成 WebUI 静态文件
---
## 四、下载 Release 构建包
仓库内置 GitHub Actions 工作流:`.github/workflows/release-artifacts.yml`
- **触发条件**:仅在 Release `published` 时触发(普通 push 不会构建)
- **构建产物**:多平台二进制压缩包 + `sha256sums.txt`
| 平台 | 架构 | 文件格式 |
| --- | --- | --- |
| Linux | amd64, arm64 | `.tar.gz` |
| macOS | amd64, arm64 | `.tar.gz` |
| Windows | amd64 | `.zip` |
每个压缩包包含:
- `ds2api` 可执行文件Windows 为 `ds2api.exe`
- `static/admin/`WebUI 构建产物)
- `sha3_wasm_bg.7b9ca65ddd.wasm`
- `config.example.json``.env.example`
- `README.MD``README.en.md``LICENSE`
### 使用步骤
```bash
# 1. 下载对应平台的压缩包
# 2. 解压
tar -xzf ds2api_v1.7.0_linux_amd64.tar.gz
cd ds2api_v1.7.0_linux_amd64
# 3. 配置
cp config.example.json config.json
# 编辑 config.json
# 4. 启动
./ds2api
```
若你在项目设置里手动改过 Output Directory请同步改为 `static` 或清空让仓库配置生效。
### 维护者发布步骤
若接口返回 Vercel 的 HTML 页面 `Authentication Required`(而不是 JSON说明被 Vercel Deployment Protection 拦截:
1. 在 GitHub 创建并发布 Release带 tag`v1.7.0`
2. 等待 Actions 工作流 `Release Artifacts` 完成
3. 在 Release 的 Assets 下载对应平台压缩包
- 关闭该部署/环境的 Protection推荐用于公开 API
- 或给请求加 `x-vercel-protection-bypass`
- 若仅是 Vercel 内部 Node->Go 调用被拦截,可设置 `VERCEL_AUTOMATION_BYPASS_SECRET`(或 `DS2API_VERCEL_PROTECTION_BYPASS`
---
Vercel 流式说明(重要):
## 五、反向代理Nginx
- Vercel 的 Go Runtime 存在平台层响应缓冲,因此本项目在 Vercel 上采用“Go prepare + Node stream”的混合链路来恢复实时 SSE。
- 该适配只在 Vercel 生效;本地与 Docker 仍走纯 Go 链路。
## 4. 反向代理Nginx
如果在 Nginx 后挂载,建议关闭缓冲以保证 SSE
如果在 Nginx 后部署,**必须关闭缓冲**以保证 SSE 流式响应正常工作:
```nginx
location / {
@@ -180,9 +336,50 @@ location / {
}
```
## 5. systemd 示例Linux
如果需要 HTTPS可以在 Nginx 层配置 SSL 证书:
```nginx
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:5001;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
tcp_nodelay on;
}
}
```
---
## 六、Linux systemd 服务化
### 6.1 安装
```bash
# 将编译好的二进制文件和相关文件复制到目标目录
sudo mkdir -p /opt/ds2api
sudo cp ds2api config.json sha3_wasm_bg.7b9ca65ddd.wasm /opt/ds2api/
sudo cp -r static/admin /opt/ds2api/static/admin
```
### 6.2 创建 systemd 服务文件
```ini
# /etc/systemd/system/ds2api.service
[Unit]
Description=DS2API (Go)
After=network.target
@@ -192,7 +389,7 @@ Type=simple
WorkingDirectory=/opt/ds2api
Environment=PORT=5001
Environment=DS2API_CONFIG_PATH=/opt/ds2api/config.json
Environment=DS2API_ADMIN_KEY=admin
Environment=DS2API_ADMIN_KEY=your-admin-key-here
ExecStart=/opt/ds2api/ds2api
Restart=always
RestartSec=5
@@ -201,38 +398,72 @@ RestartSec=5
WantedBy=multi-user.target
```
常用命令
### 6.3 常用命令
```bash
# 加载服务配置
sudo systemctl daemon-reload
# 设置开机自启
sudo systemctl enable ds2api
# 启动服务
sudo systemctl start ds2api
# 查看状态
sudo systemctl status ds2api
# 查看日志
sudo journalctl -u ds2api -f
# 重启服务
sudo systemctl restart ds2api
# 停止服务
sudo systemctl stop ds2api
```
## 6. 部署后检查
---
## 七、部署后检查
无论使用哪种部署方式,启动后建议依次检查:
```bash
# 1. 存活探针
curl -s http://127.0.0.1:5001/healthz
# 预期: {"status":"ok"}
# 2. 就绪探针
curl -s http://127.0.0.1:5001/readyz
# 预期: {"status":"ready"}
# 3. 模型列表
curl -s http://127.0.0.1:5001/v1/models
# 预期: {"object":"list","data":[...]}
# 4. 管理台页面(如果已构建 WebUI
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5001/admin
# 预期: 200
# 5. 测试 API 调用
curl http://127.0.0.1:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"hello"}]}'
```
如果你依赖管理台接口,再检查:
---
```bash
curl -s http://127.0.0.1:5001/admin
```
## 八、发布前进行本地回归
## 7. 发布前本地回归(推荐)
建议在发布前执行一次完整测试集(真实账号链路):
建议在发布前执行完整的端到端测试集(使用真实账号):
```bash
./scripts/testsuite/run-live.sh
```
选参数示例
自定义参数
```bash
go run ./cmd/ds2api-tests \
@@ -243,9 +474,11 @@ go run ./cmd/ds2api-tests \
--retries 2
```
测试集自动执行:
测试集自动执行内容
- 语法/构建/单测 preflight
- 隔离副本配置启动服务(不污染原始 `config.json`
- 真实调用场景验证OpenAI/Claude/Admin/并发/toolcall/流式)
- 全量请求与响应日志落盘(用于故障复盘)
- 语法/构建/单测 preflight
- 隔离副本配置启动服务(不污染原始 `config.json`
- 真实调用场景验证OpenAI/Claude/Admin/并发/toolcall/流式)
- 全量请求与响应日志落盘(用于故障复盘)
详细测试集说明参阅 [TESTING.md](TESTING.md)。

293
README.MD
View File

@@ -8,23 +8,44 @@
语言 / Language: [中文](README.MD) | [English](README.en.md)
将 DeepSeek Web 对话能力转换为 OpenAI 与 Claude 兼容 API。当前仓库后端为 **Go 全量实现**,前端保留 React WebUI源码在 `webui/`,部署时自动构建到 `static/admin`)。
将 DeepSeek Web 对话能力转换为 OpenAI 与 Claude 兼容 API。后端为 **Go 全量实现**,前端 React WebUI 管理台(源码在 `webui/`,部署时自动构建到 `static/admin`)。
## 当前实现边界
## 架构概览
- 后端Go`cmd/`, `api/`, `internal/`),不再依赖 Python 运行时
- 前端React 管理台(源码在 `webui/`,运行时托管静态构建)
- 部署本地运行、Docker、Vercel Serverless
```text
┌──────────────┐ ┌──────────────────────────────────────┐
│ 客户端 │ │ DS2API │
│ (OpenAI / │───▶│ ┌────────┐ ┌──────────┐ ┌──────┐ │
│ Claude │ │ │鉴权中间件│─▶│适配器层 │─▶│DeepSeek│
│ 兼容) │ │ └────────┘ │OpenAI/ │ │Client │ │
│ │◀───│ │Claude │ └──────┘ │
│ │ │ ┌────────┐ └──────────┘ │
│ │ │ │Admin API│ ┌──────────┐ │
│ │ │ └────────┘ │账号池/队列│ │
│ │ │ ┌────────┐ └──────────┘ │
│ │ │ │WebUI │ ┌──────────┐ │
│ │ │ │(/admin)│ │PoW WASM │ │
│ │ │ └────────┘ └──────────┘ │
└──────────────┘ └──────────────────────────────────────┘
```
- **后端**Go`cmd/ds2api/`、`api/`、`internal/`),不依赖 Python 运行时
- **前端**React 管理台(`webui/`),运行时托管静态构建产物
- **部署**本地运行、Docker、Vercel Serverless、Linux systemd
## 核心能力
- OpenAI 兼容:`/v1/models`、`/v1/chat/completions`
- Claude 兼容:`/anthropic/v1/models`、`/anthropic/v1/messages`、`/anthropic/v1/messages/count_tokens`
- 多账号轮询与自动 token 刷新
- DeepSeek PoWWASM计算
- Admin API配置管理、账号测试、导入导出、Vercel 同步
- WebUI`/admin` 单页应用托管
- 运维探针:`/healthz`、`/readyz`
| 能力 | 说明 |
| --- | --- |
| OpenAI 兼容 | `GET /v1/models`、`POST /v1/chat/completions`(流式/非流式) |
| Claude 兼容 | `GET /anthropic/v1/models`、`POST /anthropic/v1/messages`、`POST /anthropic/v1/messages/count_tokens` |
| 多账号轮询 | 自动 token 刷新、邮箱/手机号双登录方式 |
| 并发队列控制 | 每账号 in-flight 上限 + 等待队列,动态计算建议并发值 |
| DeepSeek PoW | WASM 计算(`wazero`),无需外部 Node.js 依赖 |
| Tool Calling | 防泄漏处理:自动缓冲、识别、结构化输出 |
| Admin API | 配置管理、账号测试 / 批量测试、导入导出、Vercel 同步 |
| WebUI 管理台 | `/admin` 单页应用(中英文双语、深色模式) |
| 运维探针 | `GET /healthz`(存活)、`GET /readyz`(就绪) |
## 模型支持
@@ -32,10 +53,10 @@
| 模型 | thinking | search |
| --- | --- | --- |
| `deepseek-chat` | false | false |
| `deepseek-reasoner` | true | false |
| `deepseek-chat-search` | false | true |
| `deepseek-reasoner-search` | true | true |
| `deepseek-chat` | ❌ | ❌ |
| `deepseek-reasoner` | ✅ | ❌ |
| `deepseek-chat-search` | ❌ | ✅ |
| `deepseek-reasoner-search` | ✅ | ✅ |
### Claude 接口
@@ -45,84 +66,68 @@
| `claude-sonnet-4-20250514-fast` | `deepseek-chat` |
| `claude-sonnet-4-20250514-slow` | `deepseek-reasoner` |
可通过配置中的 `claude_mapping` 或 `claude_model_mapping` 覆盖映射。
可通过配置中的 `claude_mapping` 或 `claude_model_mapping` 覆盖映射关系
## 快速开始
### 1) 本地运行
### 方式一:本地运行
要求Go 1.24+
**前置要求**Go 1.24+Node.js 20+(仅在需要构建 WebUI 时)
```bash
# 1. 克隆仓库
git clone https://github.com/CJackHwang/ds2api.git
cd ds2api
# 2. 配置
cp config.example.json config.json
# 编辑 config.json
# 编辑 config.json,填入你的 DeepSeek 账号信息和 API key
# 3. 启动
go run ./cmd/ds2api
```
默认地址:`http://localhost:5001`
默认监听地址:`http://localhost:5001`
本地默认会在启动时自动尝试构建 WebUI需要本机有 Node.js/npm
若你想手动构建,也可执行:
```bash
./scripts/build-webui.sh
```
### 2) Docker 运行
> **WebUI 自动构建**:本地首次启动时,若 `static/admin` 不存在,会自动尝试执行 `npm install && npm run build`(需要本机有 Node.js。你也可以手动构建`./scripts/build-webui.sh`
### 方式二Docker 运行
```bash
# 1. 配置环境变量
cp .env.example .env
# 编辑 .env
# 2. 启动
docker-compose up -d
# 3. 查看日志
docker-compose logs -f
```
### 3) Vercel 部署
更新镜像:`docker-compose up -d --build`
- 入口:`api/index.go`
- 路由重写:`vercel.json`
- `vercel.json` 会在构建阶段自动执行 `npm ci --prefix webui && npm run build --prefix webui`
- `/v1/chat/completions` 在 Vercel 上默认走 `api/chat-stream.js`Node Runtime以保证实时 SSE
- `api/chat-stream.js` 仅负责流式数据转发;鉴权、账号选择、会话/PoW 准备仍由 Go 内部 prepare 接口处理
- Go prepare 会下发 `lease_id`Node 在流结束后调用 release确保账号占用时长与 Go 原生流式一致
- WebUI 的“非流式测试”会直接请求 `?__go=1`,避免 Vercel 上 Node 中转导致长请求更易超时
- 至少配置:
- `DS2API_ADMIN_KEY`
- `DS2API_CONFIG_JSON`JSON 字符串或 Base64
### 方式三Vercel 部署
说明:仓库不提交 `static/admin` 构建产物Vercel 构建时自动生成并打包。
1. Fork 仓库到自己的 GitHub
2. 在 Vercel 上导入项目
3. 配置环境变量(至少设置 `DS2API_ADMIN_KEY` 和 `DS2API_CONFIG_JSON`
4. 部署
## Release 自动构建产物GitHub Actions
> **流式说明**`/v1/chat/completions` 在 Vercel 上默认走 `api/chat-stream.js`Node Runtime以保证实时 SSE。鉴权、账号选择、会话/PoW 准备仍由 Go 内部 prepare 接口完成Node 端仅转发流数据。
仓库内置工作流:`.github/workflows/release-artifacts.yml`
详细部署说明请参阅 [部署指南](DEPLOY.md)。
- 触发条件:仅在 GitHub Release `published` 时触发
- 不会在普通 `push` 时构建
- 构建内容多平台二进制包Linux/macOS/Windows+ `sha256sums.txt`
- 每个压缩包包含:
- `ds2api` 可执行文件Windows 为 `ds2api.exe`
- `static/admin`WebUI 构建产物)
- `sha3_wasm_bg.7b9ca65ddd.wasm`
- `config.example.json`、`.env.example`
- `README.MD`、`README.en.md`、`LICENSE`
### 方式四:下载 Release 构建包
维护者发布步骤
1. 在 GitHub 创建并发布 Release带 tag如 `v1.7.0`
2. 等待 Actions 工作流 `Release Artifacts` 完成
3. 在 Release 的 Assets 下载对应平台压缩包
下载后运行示例Linux/macOS
每次发布 Release 时GitHub Actions 会自动构建多平台二进制包
```bash
# 下载对应平台的压缩包后
tar -xzf ds2api_v1.7.0_linux_amd64.tar.gz
cd ds2api_v1.7.0_linux_amd64
cp config.example.json config.json
# 编辑 config.json
./ds2api
```
@@ -152,79 +157,133 @@ cp config.example.json config.json
}
```
### 环境变量(核心)
- `keys`API 访问密钥列表,客户端通过 `Authorization: Bearer <key>` 鉴权
- `accounts`DeepSeek 账号列表,支持 `email` 或 `mobile` 登录
- `token`:留空则首次请求时自动登录获取;也可预填已有 token
- `claude_model_mapping`:字典中 `fast`/`slow` 后缀映射到对应 DeepSeek 模型
| 变量 | 用途 |
### 环境变量
| 变量 | 用途 | 默认值 |
| --- | --- | --- |
| `PORT` | 服务端口 | `5001` |
| `LOG_LEVEL` | 日志级别 | `INFO`(可选:`DEBUG`/`WARN`/`ERROR` |
| `DS2API_ADMIN_KEY` | Admin 登录密钥 | `admin` |
| `DS2API_JWT_SECRET` | Admin JWT 签名密钥 | 等同 `DS2API_ADMIN_KEY` |
| `DS2API_JWT_EXPIRE_HOURS` | Admin JWT 过期小时数 | `24` |
| `DS2API_CONFIG_PATH` | 配置文件路径 | `config.json` |
| `DS2API_CONFIG_JSON` | 直接注入配置JSON 或 Base64 | — |
| `DS2API_WASM_PATH` | PoW WASM 文件路径 | 自动查找 |
| `DS2API_STATIC_ADMIN_DIR` | 管理台静态文件目录 | `static/admin` |
| `DS2API_AUTO_BUILD_WEBUI` | 启动时自动构建 WebUI | 本地开启Vercel 关闭 |
| `DS2API_ACCOUNT_MAX_INFLIGHT` | 每账号最大并发 in-flight 请求数 | `2` |
| `DS2API_ACCOUNT_CONCURRENCY` | 同上(兼容旧名) | — |
| `DS2API_ACCOUNT_MAX_QUEUE` | 等待队列上限 | `recommended_concurrency` |
| `DS2API_ACCOUNT_QUEUE_SIZE` | 同上(兼容旧名) | — |
| `DS2API_VERCEL_INTERNAL_SECRET` | Vercel 混合流式内部鉴权密钥 | 回退用 `DS2API_ADMIN_KEY` |
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | 流式 lease 过期秒数 | `900` |
| `VERCEL_TOKEN` | Vercel 同步 token | — |
| `VERCEL_PROJECT_ID` | Vercel 项目 ID | — |
| `VERCEL_TEAM_ID` | Vercel 团队 ID | — |
## 鉴权模式
调用业务接口(`/v1/*`、`/anthropic/*`)时支持两种模式:
| 模式 | 说明 |
| --- | --- |
| `PORT` | 服务端口,默认 `5001` |
| `LOG_LEVEL` | 日志级别:`DEBUG/INFO/WARN/ERROR` |
| `DS2API_ACCOUNT_MAX_INFLIGHT` | 每个账号最大并发 in-flight 请求数,默认 `2` |
| `DS2API_ACCOUNT_CONCURRENCY` | 同上别名(兼容旧写法) |
| `DS2API_ACCOUNT_MAX_QUEUE` | 等待队列上限,默认等于 `recommended_concurrency` |
| `DS2API_ACCOUNT_QUEUE_SIZE` | 同上别名(兼容旧写法) |
| `DS2API_ADMIN_KEY` | Admin 登录密钥,默认 `admin` |
| `DS2API_JWT_SECRET` | Admin JWT 签名密钥(可选) |
| `DS2API_JWT_EXPIRE_HOURS` | Admin JWT 过期小时数,默认 `24` |
| `DS2API_CONFIG_PATH` | 配置文件路径,默认 `config.json` |
| `DS2API_CONFIG_JSON` | 直接注入配置JSON 或 Base64 |
| `DS2API_WASM_PATH` | PoW wasm 文件路径 |
| `DS2API_STATIC_ADMIN_DIR` | 管理台静态文件目录 |
| `DS2API_AUTO_BUILD_WEBUI` | 启动时缺失 WebUI 时是否自动执行 npm build默认本地开启Vercel 关闭) |
| `DS2API_VERCEL_INTERNAL_SECRET` | Vercel 混合流式链路内部鉴权密钥(可选;未设置时回退用 `DS2API_ADMIN_KEY` |
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | Vercel 流式 lease 过期秒数(默认 `900` |
| `VERCEL_TOKEN` | Vercel 同步 token可选 |
| `VERCEL_PROJECT_ID` | Vercel 项目 ID可选 |
| `VERCEL_TEAM_ID` | Vercel 团队 ID可选 |
| **托管账号模式** | `Bearer` 或 `x-api-key` 传入 `config.keys` 中的 key由服务自动轮询选择账号 |
| **直通 token 模式** | 传入 token 不在 `config.keys` 中时,直接作为 DeepSeek token 使用 |
## 鉴权与账号模式
可选请求头 `X-Ds2-Target-Account`:指定使用某个托管账号(值为 email 或 mobile
调用业务接口时(`/v1/*`, `/anthropic/*`)支持两种模式:
## 并发模型
1. 托管账号模式:`Bearer` 或 `x-api-key` 使用 `config.keys` 中的 key。
2. 直通 token 模式:当传入 token 不在 `config.keys` 中时,服务直接把它当作 DeepSeek token 使用。
```
每账号可用并发 = DS2API_ACCOUNT_MAX_INFLIGHT默认 2
建议并发值 = 账号数量 × 每账号并发上限
等待队列上限 = DS2API_ACCOUNT_MAX_QUEUE默认 = 建议并发值)
429 阈值 = in-flight + 等待队列 ≈ 账号数量 × 4
```
可选请求头:`X-Ds2-Target-Account`,用于指定托管账号。
- 当 in-flight 槽位满时,请求进入等待队列,**不会立即 429**
- 超出总承载上限后才返回 `429 Too Many Requests`
- `GET /admin/queue/status` 返回实时并发状态
## 并发建议值
## Tool Call 适配
- 系统建议并发值按账号池动态计算:`账号数量 × 每账号并发上限`
- 默认每账号并发上限是 `2`,因此默认建议值是 `账号数量 × 2`
- 当 in-flight 槽位满时,请求会进入等待队列,不会立即 429
- 默认等待队列上限 = `recommended_concurrency`,因此默认总承载上限是 `账号数量 × 4`
- 超过总承载上限in-flight + waiting才返回 `429`
- 可通过 `DS2API_ACCOUNT_MAX_INFLIGHT`(或 `DS2API_ACCOUNT_CONCURRENCY`)手动覆盖每账号并发上限
- 可通过 `DS2API_ACCOUNT_MAX_QUEUE`(或 `DS2API_ACCOUNT_QUEUE_SIZE`)手动覆盖等待队列上限
- `GET /admin/queue/status` 会返回 `max_inflight_per_account`、`recommended_concurrency`、`waiting`、`max_queue_size`
当请求中带 `tools` 时DS2API 会做防泄漏处理:
## Tool Call 适配说明
1. `stream=true` 时先**缓冲**正文片段
2. 若识别到工具调用 → 仅输出结构化 `tool_calls`,不透传原始 JSON 文本
3. 若最终不是工具调用 → 一次性输出普通文本
4. 解析器支持混合文本、fenced JSON、`function.arguments` 字符串等格式
当前实现对 toolcall 做了防泄漏处理:
## 项目结构
- `tools` + `stream=true` 时,服务端会先缓冲正文片段
- 若识别到工具调用,会只输出结构化 `tool_calls`,不透传原始 JSON 文本
- 若最终不是工具调用,再一次性输出普通文本
- 解析器支持混合文本、fenced JSON、`function.arguments` 字符串等格式
```text
ds2api/
├── cmd/
│ ├── ds2api/ # 本地 / 容器启动入口
│ └── ds2api-tests/ # 端到端测试集入口
├── api/
│ ├── index.go # Vercel Serverless Go 入口
│ ├── chat-stream.js # Vercel Node.js 流式转发
│ └── helpers/ # Node.js 辅助模块
├── internal/
│ ├── account/ # 账号池与并发队列
│ ├── adapter/
│ │ ├── openai/ # OpenAI 兼容适配器(含 Tool Call 解析、Vercel 流式 prepare/release
│ │ └── claude/ # Claude 兼容适配器
│ ├── admin/ # Admin API handlers
│ ├── auth/ # 鉴权与 JWT
│ ├── config/ # 配置加载与热更新
│ ├── deepseek/ # DeepSeek API 客户端、PoW WASM
│ ├── server/ # HTTP 路由与中间件chi router
│ ├── sse/ # SSE 解析工具
│ ├── util/ # 通用工具函数
│ └── webui/ # WebUI 静态文件托管与自动构建
├── webui/ # React WebUI 源码Vite + Tailwind
│ └── src/
│ ├── components/ # AccountManager / ApiTester / BatchImport / VercelSync / Login / LandingPage
│ └── locales/ # 中英文语言包zh.json / en.json
├── scripts/
│ ├── build-webui.sh # WebUI 手动构建脚本
│ └── testsuite/ # 测试集运行脚本
├── static/admin/ # WebUI 构建产物(不提交到 Git
├── .github/
│ ├── workflows/ # GitHub ActionsRelease 自动构建)
│ ├── ISSUE_TEMPLATE/ # Issue 模板
│ └── PULL_REQUEST_TEMPLATE.md
├── config.example.json # 配置文件示例
├── .env.example # 环境变量示例
├── Dockerfile # 多阶段构建WebUI + Go
├── docker-compose.yml # 生产环境 Docker Compose
├── docker-compose.dev.yml # 开发环境 Docker Compose
├── vercel.json # Vercel 路由与构建配置
├── go.mod / go.sum # Go 模块依赖
└── version.txt # 版本号
```
## 文档与测试
## 文档索引
- API 文档:`API.md` / `API.en.md`
- 部署文档:`DEPLOY.md` / `DEPLOY.en.md`
- 贡献指南:`CONTRIBUTING.md` / `CONTRIBUTING.en.md`
- 测试集文档:`TESTING.md`
| 文档 | 说明 |
| --- | --- |
| [API.md](API.md) / [API.en.md](API.en.md) | API 接口文档(含请求/响应示例) |
| [DEPLOY.md](DEPLOY.md) / [DEPLOY.en.md](DEPLOY.en.md) | 部署指南(本地/Docker/Vercel/systemd |
| [CONTRIBUTING.md](CONTRIBUTING.md) / [CONTRIBUTING.en.md](CONTRIBUTING.en.md) | 贡献指南 |
| [TESTING.md](TESTING.md) | 测试集使用指南 |
## 测试
```bash
# 单元测试
go test ./...
```
一键真实账号全链路测试(生成完整请求/响应日志)
```bash
# 一键端到端全链路测试(真实账号,生成完整请求/响应日志)
./scripts/testsuite/run-live.sh
```
或使用可配置参数
```bash
# 或自定义参数
go run ./cmd/ds2api-tests \
--config config.json \
--admin-key admin \
@@ -233,6 +292,14 @@ go run ./cmd/ds2api-tests \
--retries 2
```
## Release 自动构建GitHub Actions
工作流文件:`.github/workflows/release-artifacts.yml`
- **触发条件**:仅在 GitHub Release `published` 时触发(普通 push 不会触发)
- **构建产物**:多平台二进制包(`linux/amd64`、`linux/arm64`、`darwin/amd64`、`darwin/arm64`、`windows/amd64`+ `sha256sums.txt`
- **每个压缩包包含**`ds2api` 可执行文件、`static/admin`、WASM 文件、配置示例、README、LICENSE
## 免责声明
本项目基于逆向方式实现,仅供学习与研究使用。稳定性和可用性不作保证,请勿用于违反服务条款或法律法规的场景。

View File

@@ -8,127 +8,132 @@
Language: [中文](README.MD) | [English](README.en.md)
DS2API converts DeepSeek Web chat capability into OpenAI-compatible and Claude-compatible APIs. The current repository is **Go backend only** with the existing React WebUI source in `webui/` and build output generated to `static/admin` during deployment.
DS2API converts DeepSeek Web chat capability into OpenAI-compatible and Claude-compatible APIs. The backend is a **pure Go implementation**, with a React WebUI admin panel (source in `webui/`, build output auto-generated to `static/admin` during deployment).
## Implementation Boundary
## Architecture Overview
- Backend: Go (`cmd/`, `api/`, `internal/`), no Python runtime
- Frontend: React admin panel (`webui/` source, static build served at runtime)
- Deployment: local run, Docker, Vercel serverless
```text
┌──────────────┐ ┌──────────────────────────────────────┐
│ Clients │ │ DS2API │
│ (OpenAI / │───▶│ ┌────────┐ ┌──────────┐ ┌──────┐ │
│ Claude │ │ │Auth MW │─▶│Adapter │─▶│DeepSeek│
│ compat) │ │ └────────┘ │OpenAI/ │ │Client │ │
│ │◀───│ │Claude │ └──────┘ │
│ │ │ ┌────────┐ └──────────┘ │
│ │ │ │Admin API│ ┌──────────┐ │
│ │ │ └────────┘ │Account │ │
│ │ │ ┌────────┐ │Pool/Queue│ │
│ │ │ │WebUI │ └──────────┘ │
│ │ │ │(/admin)│ ┌──────────┐ │
│ │ │ └────────┘ │PoW WASM │ │
└──────────────┘ └──────────────────────────────────────┘
```
- **Backend**: Go (`cmd/ds2api/`, `api/`, `internal/`), no Python runtime
- **Frontend**: React admin panel (`webui/`), served as static build at runtime
- **Deployment**: local run, Docker, Vercel serverless, Linux systemd
## Key Capabilities
- OpenAI-compatible endpoints: `GET /v1/models`, `POST /v1/chat/completions`
- Claude-compatible endpoints: `GET /anthropic/v1/models`, `POST /anthropic/v1/messages`, `POST /anthropic/v1/messages/count_tokens`
- Multi-account rotation and automatic token refresh
- DeepSeek PoW solving via WASM
- Admin API: config management, account tests, import/export, Vercel sync
- WebUI SPA hosting at `/admin`
- Health probes: `GET /healthz`, `GET /readyz`
| Capability | Details |
| --- | --- |
| OpenAI compatible | `GET /v1/models`, `POST /v1/chat/completions` (stream/non-stream) |
| Claude compatible | `GET /anthropic/v1/models`, `POST /anthropic/v1/messages`, `POST /anthropic/v1/messages/count_tokens` |
| Multi-account rotation | Auto token refresh, email/mobile dual login |
| Concurrency control | Per-account in-flight limit + waiting queue, dynamic recommended concurrency |
| DeepSeek PoW | WASM solving via `wazero`, no external Node.js dependency |
| Tool Calling | Anti-leak handling: auto buffer, detect, structured output |
| Admin API | Config management, account testing/batch test, import/export, Vercel sync |
| WebUI Admin Panel | SPA at `/admin` (bilingual Chinese/English, dark mode) |
| Health Probes | `GET /healthz` (liveness), `GET /readyz` (readiness) |
## Model Support
### OpenAI endpoint
### OpenAI Endpoint
| Model | thinking | search |
| --- | --- | --- |
| `deepseek-chat` | false | false |
| `deepseek-reasoner` | true | false |
| `deepseek-chat-search` | false | true |
| `deepseek-reasoner-search` | true | true |
| `deepseek-chat` | ❌ | ❌ |
| `deepseek-reasoner` | ✅ | ❌ |
| `deepseek-chat-search` | ❌ | ✅ |
| `deepseek-reasoner-search` | ✅ | ✅ |
### Claude endpoint
### Claude Endpoint
| Model | Default mapping |
| Model | Default Mapping |
| --- | --- |
| `claude-sonnet-4-20250514` | `deepseek-chat` |
| `claude-sonnet-4-20250514-fast` | `deepseek-chat` |
| `claude-sonnet-4-20250514-slow` | `deepseek-reasoner` |
You can override mapping via `claude_mapping` or `claude_model_mapping` in config.
Override mapping via `claude_mapping` or `claude_model_mapping` in config.
## Quick Start
### 1) Local run
### Option 1: Local Run
Requirement: Go 1.24+
**Prerequisites**: Go 1.24+, Node.js 20+ (only if building WebUI locally)
```bash
# 1. Clone
git clone https://github.com/CJackHwang/ds2api.git
cd ds2api
# 2. Configure
cp config.example.json config.json
# edit config.json
# Edit config.json with your DeepSeek account info and API keys
# 3. Start
go run ./cmd/ds2api
```
Default URL: `http://localhost:5001`
By default, local startup will auto-build WebUI when `static/admin` is missing (Node.js/npm required).
If you prefer manual build:
```bash
./scripts/build-webui.sh
```
### 2) Docker
> **WebUI auto-build**: On first local startup, if `static/admin` is missing, DS2API will auto-run `npm install && npm run build` (requires Node.js). You can also build manually: `./scripts/build-webui.sh`
### Option 2: Docker
```bash
# 1. Configure environment
cp .env.example .env
# edit .env
# Edit .env
# 2. Start
docker-compose up -d
# 3. View logs
docker-compose logs -f
```
### 3) Vercel
Rebuild after updates: `docker-compose up -d --build`
- Entrypoint: `api/index.go`
- Rewrites: `vercel.json`
- `vercel.json` runs `npm ci --prefix webui && npm run build --prefix webui` during build
- `/v1/chat/completions` is routed to `api/chat-stream.js` (Node Runtime) on Vercel to preserve real-time SSE
- `api/chat-stream.js` is data-path only; auth/account/session/PoW preparation still comes from an internal Go prepare endpoint
- Go prepare returns a `lease_id`; Node releases it at stream end so account occupancy duration stays aligned with native Go streaming behavior
- WebUI non-stream test calls `?__go=1` directly to avoid extra Node hop timeout risk on long Vercel requests
- Minimum env vars:
- `DS2API_ADMIN_KEY`
- `DS2API_CONFIG_JSON` (raw JSON or Base64)
### Option 3: Vercel
Note: build artifacts under `static/admin` are not committed; Vercel generates them during build.
1. Fork this repo to your GitHub account
2. Import the project on Vercel
3. Set environment variables (minimum: `DS2API_ADMIN_KEY` and `DS2API_CONFIG_JSON`)
4. Deploy
## Release Artifact Automation (GitHub Actions)
> **Streaming note**: `/v1/chat/completions` on Vercel is routed to `api/chat-stream.js` (Node Runtime) for real-time SSE. Auth, account selection, session/PoW preparation are still handled by the Go internal prepare endpoint; Node only relays stream data.
Built-in workflow: `.github/workflows/release-artifacts.yml`
For detailed deployment instructions, see the [Deployment Guide](DEPLOY.en.md).
- Trigger: only when a GitHub Release is `published`
- No build on normal `push`
- Outputs: multi-platform binaries (Linux/macOS/Windows) + `sha256sums.txt`
- Each archive includes:
- `ds2api` executable (`ds2api.exe` on Windows)
- `static/admin` (built WebUI assets)
- `sha3_wasm_bg.7b9ca65ddd.wasm`
- `config.example.json`, `.env.example`
- `README.MD`, `README.en.md`, `LICENSE`
### Option 4: Download Release Binaries
Maintainer release flow:
1. Create and publish a GitHub Release (with tag, e.g. `v1.7.0`)
2. Wait for the `Release Artifacts` workflow to finish
3. Download the matching archive from Release Assets
Run from downloaded archive (Linux/macOS):
GitHub Actions automatically builds multi-platform archives on each Release:
```bash
# After downloading the archive for your platform
tar -xzf ds2api_v1.7.0_linux_amd64.tar.gz
cd ds2api_v1.7.0_linux_amd64
cp config.example.json config.json
# Edit config.json
./ds2api
```
## Configuration
### `config.json` example
### `config.json` Example
```json
{
@@ -152,79 +157,133 @@ cp config.example.json config.json
}
```
### Core environment variables
- `keys`: API access keys; clients authenticate via `Authorization: Bearer <key>`
- `accounts`: DeepSeek account list, supports `email` or `mobile` login
- `token`: Leave empty for auto-login on first request; or pre-fill an existing token
- `claude_model_mapping`: Maps `fast`/`slow` suffixes to corresponding DeepSeek models
| Variable | Purpose |
| --- | --- |
| `PORT` | Service port, default `5001` |
| `LOG_LEVEL` | `DEBUG/INFO/WARN/ERROR` |
| `DS2API_ACCOUNT_MAX_INFLIGHT` | Max in-flight requests per managed account, default `2` |
| `DS2API_ACCOUNT_CONCURRENCY` | Alias of the same setting (legacy compatibility) |
| `DS2API_ACCOUNT_MAX_QUEUE` | Waiting queue limit (managed-key mode), default=`recommended_concurrency` |
| `DS2API_ACCOUNT_QUEUE_SIZE` | Alias of the same setting (legacy compatibility) |
| `DS2API_ADMIN_KEY` | Admin login key, default `admin` |
| `DS2API_JWT_SECRET` | Admin JWT signing secret (optional) |
| `DS2API_JWT_EXPIRE_HOURS` | Admin JWT TTL in hours, default `24` |
| `DS2API_CONFIG_PATH` | Config file path, default `config.json` |
| `DS2API_CONFIG_JSON` | Inline config (JSON or Base64) |
| `DS2API_WASM_PATH` | PoW wasm path |
| `DS2API_STATIC_ADMIN_DIR` | Admin static assets dir |
| `DS2API_AUTO_BUILD_WEBUI` | Auto run npm build on startup when WebUI assets are missing (default: enabled locally, disabled on Vercel) |
| `DS2API_VERCEL_INTERNAL_SECRET` | Internal auth secret for Vercel hybrid streaming path (optional; falls back to `DS2API_ADMIN_KEY` if unset) |
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | Stream lease TTL seconds for Vercel hybrid streaming (default `900`) |
| `VERCEL_TOKEN` | Vercel sync token (optional) |
| `VERCEL_PROJECT_ID` | Vercel project ID (optional) |
| `VERCEL_TEAM_ID` | Vercel team ID (optional) |
### Environment Variables
## Auth and Account Modes
| Variable | Purpose | Default |
| --- | --- | --- |
| `PORT` | Service port | `5001` |
| `LOG_LEVEL` | Log level | `INFO` (`DEBUG`/`WARN`/`ERROR`) |
| `DS2API_ADMIN_KEY` | Admin login key | `admin` |
| `DS2API_JWT_SECRET` | Admin JWT signing secret | Same as `DS2API_ADMIN_KEY` |
| `DS2API_JWT_EXPIRE_HOURS` | Admin JWT TTL in hours | `24` |
| `DS2API_CONFIG_PATH` | Config file path | `config.json` |
| `DS2API_CONFIG_JSON` | Inline config (JSON or Base64) | — |
| `DS2API_WASM_PATH` | PoW WASM file path | Auto-detect |
| `DS2API_STATIC_ADMIN_DIR` | Admin static assets dir | `static/admin` |
| `DS2API_AUTO_BUILD_WEBUI` | Auto-build WebUI on startup | Enabled locally, disabled on Vercel |
| `DS2API_ACCOUNT_MAX_INFLIGHT` | Max in-flight requests per account | `2` |
| `DS2API_ACCOUNT_CONCURRENCY` | Alias (legacy compat) | — |
| `DS2API_ACCOUNT_MAX_QUEUE` | Waiting queue limit | `recommended_concurrency` |
| `DS2API_ACCOUNT_QUEUE_SIZE` | Alias (legacy compat) | — |
| `DS2API_VERCEL_INTERNAL_SECRET` | Vercel hybrid streaming internal auth | Falls back to `DS2API_ADMIN_KEY` |
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | Stream lease TTL seconds | `900` |
| `VERCEL_TOKEN` | Vercel sync token | — |
| `VERCEL_PROJECT_ID` | Vercel project ID | — |
| `VERCEL_TEAM_ID` | Vercel team ID | — |
## Authentication Modes
For business endpoints (`/v1/*`, `/anthropic/*`), DS2API supports two modes:
1. Managed account mode: use a key from `config.keys` via `Authorization: Bearer ...` or `x-api-key`.
2. Direct token mode: if the incoming token is not in `config.keys`, DS2API treats it as a DeepSeek token directly.
| Mode | Description |
| --- | --- |
| **Managed account** | Use a key from `config.keys` via `Authorization: Bearer ...` or `x-api-key`; DS2API auto-selects an account |
| **Direct token** | If the token is not in `config.keys`, DS2API treats it as a DeepSeek token directly |
Optional header: `X-Ds2-Target-Account` to pin one managed account.
Optional header `X-Ds2-Target-Account`: Pin a specific managed account (value is email or mobile).
## Recommended Concurrency
## Concurrency Model
- DS2API computes recommended concurrency dynamically as: `account_count * per_account_inflight_limit`
- Default per-account inflight limit is `2`, so default recommendation is `account_count * 2`
- When inflight slots are full, requests enter a waiting queue instead of immediate 429
- Default queue limit equals `recommended_concurrency`, so default 429 threshold is about `account_count * 4`
- 429 is returned only after total load exceeds `inflight + waiting` capacity
- You can override per-account inflight via `DS2API_ACCOUNT_MAX_INFLIGHT` (or `DS2API_ACCOUNT_CONCURRENCY`)
- You can override waiting queue size via `DS2API_ACCOUNT_MAX_QUEUE` (or `DS2API_ACCOUNT_QUEUE_SIZE`)
- `GET /admin/queue/status` returns `max_inflight_per_account`, `recommended_concurrency`, `waiting`, and `max_queue_size`
```
Per-account inflight = DS2API_ACCOUNT_MAX_INFLIGHT (default 2)
Recommended concurrency = account_count × per_account_inflight
Queue limit = DS2API_ACCOUNT_MAX_QUEUE (default = recommended concurrency)
429 threshold = inflight + queue ≈ account_count × 4
```
- When inflight slots are full, requests enter a waiting queue — **no immediate 429**
- 429 is returned only when total load exceeds inflight + queue capacity
- `GET /admin/queue/status` returns real-time concurrency state
## Tool Call Adaptation
Tool-call leakage is handled in the current implementation:
When `tools` is present in the request, DS2API performs anti-leak handling:
- With `tools` + `stream=true`, DS2API buffers text deltas first
- If a tool call is detected, DS2API returns structured `tool_calls` only
- If no tool call is detected, DS2API emits the buffered text once
- Parser supports mixed text, fenced JSON, and `function.arguments` payloads
1. With `stream=true`, DS2API **buffers** text deltas first
2. If a tool call is detected → only structured `tool_calls` are emitted, raw JSON is not leaked
3. If no tool call → buffered text is emitted at once
4. Parser supports mixed text, fenced JSON, and `function.arguments` payloads
## Docs and Testing
## Project Structure
- API docs: `API.md` / `API.en.md`
- Deployment docs: `DEPLOY.md` / `DEPLOY.en.md`
- Contributing: `CONTRIBUTING.md` / `CONTRIBUTING.en.md`
- Testsuite guide: `TESTING.md`
```text
ds2api/
├── cmd/
│ ├── ds2api/ # Local / container entrypoint
│ └── ds2api-tests/ # End-to-end testsuite entrypoint
├── api/
│ ├── index.go # Vercel Serverless Go entry
│ ├── chat-stream.js # Vercel Node.js stream relay
│ └── helpers/ # Node.js helper modules
├── internal/
│ ├── account/ # Account pool and concurrency queue
│ ├── adapter/
│ │ ├── openai/ # OpenAI adapter (incl. tool call parsing, Vercel stream prepare/release)
│ │ └── claude/ # Claude adapter
│ ├── admin/ # Admin API handlers
│ ├── auth/ # Auth and JWT
│ ├── config/ # Config loading and hot-reload
│ ├── deepseek/ # DeepSeek API client, PoW WASM
│ ├── server/ # HTTP routing and middleware (chi router)
│ ├── sse/ # SSE parsing utilities
│ ├── util/ # Common utilities
│ └── webui/ # WebUI static file serving and auto-build
├── webui/ # React WebUI source (Vite + Tailwind)
│ └── src/
│ ├── components/ # AccountManager / ApiTester / BatchImport / VercelSync / Login / LandingPage
│ └── locales/ # Language packs (zh.json / en.json)
├── scripts/
│ ├── build-webui.sh # Manual WebUI build script
│ └── testsuite/ # Testsuite runner scripts
├── static/admin/ # WebUI build output (not committed to Git)
├── .github/
│ ├── workflows/ # GitHub Actions (Release artifact automation)
│ ├── ISSUE_TEMPLATE/ # Issue templates
│ └── PULL_REQUEST_TEMPLATE.md
├── config.example.json # Config file template
├── .env.example # Environment variable template
├── Dockerfile # Multi-stage build (WebUI + Go)
├── docker-compose.yml # Production Docker Compose
├── docker-compose.dev.yml # Development Docker Compose
├── vercel.json # Vercel routing and build config
├── go.mod / go.sum # Go module dependencies
└── version.txt # Version number
```
## Documentation Index
| Document | Description |
| --- | --- |
| [API.md](API.md) / [API.en.md](API.en.md) | API reference with request/response examples |
| [DEPLOY.md](DEPLOY.md) / [DEPLOY.en.md](DEPLOY.en.md) | Deployment guide (local/Docker/Vercel/systemd) |
| [CONTRIBUTING.md](CONTRIBUTING.md) / [CONTRIBUTING.en.md](CONTRIBUTING.en.md) | Contributing guide |
| [TESTING.md](TESTING.md) | Testsuite guide |
## Testing
```bash
# Unit tests
go test ./...
```
One-command live end-to-end tests (with full request/response logs):
```bash
# One-command live end-to-end tests (real accounts, full request/response logs)
./scripts/testsuite/run-live.sh
```
Or run with explicit flags:
```bash
# Or with custom flags
go run ./cmd/ds2api-tests \
--config config.json \
--admin-key admin \
@@ -233,6 +292,14 @@ go run ./cmd/ds2api-tests \
--retries 2
```
## Release Artifact Automation (GitHub Actions)
Workflow: `.github/workflows/release-artifacts.yml`
- **Trigger**: only on GitHub Release `published` (normal pushes do not trigger builds)
- **Outputs**: multi-platform archives (`linux/amd64`, `linux/arm64`, `darwin/amd64`, `darwin/arm64`, `windows/amd64`) + `sha256sums.txt`
- **Each archive includes**: `ds2api` executable, `static/admin`, WASM file, config template, README, LICENSE
## Disclaimer
This project is built through reverse engineering and is provided for learning and research only. Stability is not guaranteed. Do not use it in scenarios that violate terms of service or laws.

View File

@@ -1,33 +1,57 @@
# DS2API Testing Guide
# DS2API 测试指南
## Overview
语言 / Language: [中文 + English](TESTING.md)
DS2API provides a live end-to-end testsuite that runs against your **local configured accounts** and records full artifacts for post-mortem debugging.
## 概述 | Overview
Entry points:
DS2API 提供两个层级的测试:
- `./scripts/testsuite/run-live.sh`
- `go run ./cmd/ds2api-tests`
| 层级 | 命令 | 说明 |
| --- | --- | --- |
| 单元测试 | `go test ./...` | 不需要真实账号 |
| 端到端测试 | `./scripts/testsuite/run-live.sh` | 使用真实账号执行全链路测试 |
## Quick Start
端到端测试集会录制完整的请求/响应日志,用于故障排查。
---
## 快速开始 | Quick Start
### 单元测试 | Unit Tests
```bash
go test ./...
```
### 端到端测试 | End-to-End Tests
```bash
./scripts/testsuite/run-live.sh
```
Default behavior:
**默认行为**
- runs preflight checks:
- `go test ./... -count=1`
- `node --check api/chat-stream.js`
- `node --check api/helpers/stream-tool-sieve.js`
- `npm run build --prefix webui`
- copies `config.json` into an isolated temporary config
- starts local server with `go run ./cmd/ds2api`
- executes live scenarios (OpenAI/Claude/Admin/stream/toolcall/concurrency)
- continues on failures and writes final summary
1. **Preflight 检查**
- `go test ./... -count=1`(单元测试)
- `node --check api/chat-stream.js`(语法检查)
- `node --check api/helpers/stream-tool-sieve.js`(语法检查)
- `npm run build --prefix webui`WebUI 构建检查)
## CLI Flags
2. **隔离启动**:复制 `config.json` 到临时目录,启动独立服务进程
3. **场景测试**
- ✅ OpenAI 非流式 / 流式
- ✅ Claude 非流式 / 流式
- ✅ Admin API登录 / 配置 / 账号管理)
- ✅ Tool Calling
- ✅ 并发压力测试
- ✅ Search 模型
4. **结果收集**:继续执行所有用例(不中断),写入最终汇总
---
## CLI 参数 | CLI Flags
```bash
go run ./cmd/ds2api-tests \
@@ -40,56 +64,108 @@ go run ./cmd/ds2api-tests \
--no-preflight=false
```
- `--config`: config file path (default `config.json`)
- `--admin-key`: admin key (default from `DS2API_ADMIN_KEY`, fallback `admin`)
- `--out`: artifact root directory (default `artifacts/testsuite`)
- `--port`: test server port (`0` = auto pick free port)
- `--timeout`: per request timeout in seconds (default `120`)
- `--retries`: retry count for network/5xx requests (default `2`)
- `--no-preflight`: skip preflight checks
| 参数 | 说明 | 默认值 |
| --- | --- | --- |
| `--config` | 配置文件路径 | `config.json` |
| `--admin-key` | Admin 密钥 | `DS2API_ADMIN_KEY` 环境变量,回退 `admin` |
| `--out` | 产物输出根目录 | `artifacts/testsuite` |
| `--port` | 测试服务端口(`0` = 自动分配空闲端口) | `0` |
| `--timeout` | 单个请求超时秒数 | `120` |
| `--retries` | 网络/5xx 请求重试次数 | `2` |
| `--no-preflight` | 跳过 preflight 检查 | `false` |
## Artifact Layout
---
Each run creates:
## 产物结构 | Artifact Layout
`artifacts/testsuite/<run_id>/`
每次运行会创建一个以运行 ID 命名的目录:
- `summary.json`: machine-readable report
- `summary.md`: human-readable report
- `server.log`: server stdout/stderr log during run
- `preflight.log`: preflight command outputs
- `cases/<case_id>/`
- `request.json`
- `response.headers`
- `response.body`
- `stream.raw`
- `assertions.json`
- `meta.json`
```text
artifacts/testsuite/<run_id>/
├── summary.json # 机器可读报告
├── summary.md # 人类可读报告
├── server.log # 测试期间服务端日志
├── preflight.log # Preflight 命令输出
└── cases/
└── <case_id>/
├── request.json # 请求体
├── response.headers # 响应头
├── response.body # 响应体
├── stream.raw # 原始 SSE 数据(流式用例)
├── assertions.json # 断言结果
└── meta.json # 元信息(耗时、状态码等)
```
## Trace Binding (for fast debugging)
---
Each request includes:
## Trace 关联 | Trace Binding
- header: `X-Ds2-Test-Trace: <trace_id>`
- query: `__trace_id=<trace_id>`
每个测试请求自动注入 trace 信息,便于快速定位问题:
When a case fails, `summary.md` includes trace IDs. You can locate related server logs quickly:
| 位置 | 格式 |
| --- | --- |
| 请求头 | `X-Ds2-Test-Trace: <trace_id>` |
| 查询参数 | `__trace_id=<trace_id>` |
当用例失败时,`summary.md` 中会包含 trace ID。你可以快速搜索对应的服务端日志
```bash
rg "<trace_id>" artifacts/testsuite/<run_id>/server.log
```
## Exit Code
---
- `0`: all cases passed
- `1`: one or more cases failed
## 退出码 | Exit Code
This allows using the testsuite as a local release gate.
| 退出码 | 含义 |
| --- | --- |
| `0` | 所有用例通过 ✅ |
| `1` | 有用例失败 ❌ |
## Sensitive Data Warning
可将测试集作为本地发布门禁使用CI/CD 集成)。
This testsuite stores **full raw request/response payloads** for debugging.
---
- Do not upload artifacts publicly.
- Do not share artifact directories in issue trackers without manual redaction.
## 安全提醒 | Sensitive Data Warning
⚠️ 测试集会存储**完整的原始请求/响应载荷**用于调试。
- **不要**将 artifacts 目录上传到公开仓库
- **不要**在 Issue tracker 中分享未脱敏的 artifact 文件
- 如需分享日志请先手动清除敏感信息token、密码等
---
## 常见用法 | Common Usage
### 仅跑单元测试
```bash
go test ./...
```
### 跑端到端测试(跳过 preflight
```bash
go run ./cmd/ds2api-tests --no-preflight
```
### 指定输出目录和超时
```bash
go run ./cmd/ds2api-tests \
--out /tmp/ds2api-test \
--timeout 60
```
### 在 CI 中使用
```bash
# 确保 config.json 存在且包含有效测试账号
./scripts/testsuite/run-live.sh
exit_code=$?
if [ $exit_code -ne 0 ]; then
echo "Tests failed! Check artifacts for details."
exit 1
fi
```