Files
chatgpt-codex-router/plan.md
mars 0dd6fe2c7d feat: 实现ChatGPT Codex路由器的核心功能
- 添加完整的项目基础结构,包括配置、类型定义和常量
- 实现OAuth认证流程和令牌管理
- 开发请求转换和响应处理逻辑
- 添加SSE流处理和ChatCompletions API转换
- 实现模型映射和提示指令系统
- 包含Docker部署配置和快速启动文档
- 添加自动登录功能和测试脚本
2026-01-07 10:51:54 +08:00

775 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# chatgpt-codex-router 项目计划
## 项目概览
**项目目标:** 创建一个独立的 OpenAI 兼容 API 服务器,通过 OAuth 认证将请求转发到 ChatGPT 后端,支持流式传输和详细的日志记录。
**支持的模型GPT-5.x 系列):**
- `gpt-5.1` (none/low/medium/high)
- `gpt-5.2` (none/low/medium/high/xhigh)
- `gpt-5.1-codex` (low/medium/high)
- `gpt-5.1-codex-max` (low/medium/high/xhigh)
- `gpt-5.1-codex-mini` (medium/high)
- `gpt-5.2-codex` (low/medium/high/xhigh)
---
## 项目结构
```
chatgpt-codex-router/
├── src/
│ ├── index.ts # 服务器入口
│ ├── server.ts # Hono 服务器配置
│ ├── router.ts # API 路由定义
│ ├── config.ts # 配置管理
│ ├── logger.ts # 日志系统
│ ├── auth/ # 认证模块
│ │ ├── oauth.ts # OAuth 流程逻辑
│ │ ├── token-storage.ts # Token 本地 JSON 存储
│ │ ├── token-refresh.ts # Token 刷新逻辑
│ │ ├── server.ts # 本地 OAuth 回调服务器
│ │ └── browser.ts # 浏览器打开工具
│ ├── request/ # 请求处理
│ │ ├── transformer.ts # 请求体转换
│ │ ├── headers.ts # Header 生成
│ │ ├── validator.ts # 请求验证
│ │ ├── model-map.ts # 模型映射
│ │ └── reasoning.ts # 推理参数配置
│ ├── response/ # 响应处理
│ │ ├── handler.ts # 响应处理器
│ │ ├── sse-parser.ts # SSE 流解析
│ │ ├── converter.ts # 响应格式转换
│ │ └── chat-completions.ts # Chat Completions 格式转换器
│ ├── prompts/ # 内置 Codex Prompts
│ │ ├── gpt-5-1.md # GPT-5.1 系统提示
│ │ ├── gpt-5-2.md # GPT-5.2 系统提示
│ │ ├── gpt-5-1-codex.md # GPT-5.1 Codex 系统提示
│ │ ├── gpt-5-1-codex-max.md # GPT-5.1 Codex Max 系统提示
│ │ ├── gpt-5-1-codex-mini.md # GPT-5.1 Codex Mini 系统提示
│ │ ├── gpt-5-2-codex.md # GPT-5.2 Codex 系统提示
│ │ └── index.ts # Prompt 加载器
│ ├── constants.ts # 常量定义
│ └── types.ts # TypeScript 类型定义
├── public/
│ └── oauth-success.html # OAuth 成功页面
├── docker/
│ ├── Dockerfile # Docker 镜像配置
│ ├── docker-compose.yml # Docker Compose 配置
│ └── .dockerignore # Docker 忽略文件
├── logs/ # 日志输出目录(.gitignore
├── data/ # 数据目录(.gitignore存储 tokens
├── package.json
├── tsconfig.json
├── .gitignore
└── README.md
```
---
## 核心功能模块详解
### 1. 认证模块 (`src/auth/`)
#### 1.1 OAuth 流程 (`oauth.ts`)
**功能:**
- PKCE challenge 生成
- 授权 URL 构建
- Authorization code 交换为 tokens
- JWT 解析获取 account_id
**关键常量:**
```typescript
CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"
AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize"
TOKEN_URL = "https://auth.openai.com/oauth/token"
REDIRECT_URI = "http://localhost:1455/auth/callback"
SCOPE = "openid profile email offline_access"
```
#### 1.2 Token 存储 (`token-storage.ts`)
**功能:**
- 读取/保存/删除 tokens
- 存储路径:`data/tokens.json`
- 存储格式:
```json
{
"access_token": "...",
"refresh_token": "...",
"expires_at": 1234567890,
"account_id": "...",
"updated_at": 1234567890
}
```
#### 1.3 Token 刷新 (`token-refresh.ts`)
**功能:**
- 检查 token 是否过期(提前 5 分钟刷新)
- 自动刷新过期的 token
- 更新本地存储
- 刷新失败时抛出错误
#### 1.4 本地 OAuth 服务器 (`server.ts`)
**功能:**
- 监听 `http://127.0.0.1:1455/auth/callback`
- 接收并验证 authorization code
- 返回 OAuth 成功页面
- Polling 机制(最多等待 60 秒)
#### 1.5 浏览器工具 (`browser.ts`)
**功能:**
- 跨平台浏览器打开macOS/Linux/Windows
- 静默失败(用户可手动复制 URL
---
### 2. 请求处理模块 (`src/request/`)
#### 2.1 请求体转换 (`transformer.ts`)
**核心转换逻辑:**
```typescript
// 原始请求
{
"model": "gpt-5.2-codex",
"messages": [...],
"stream": false,
"temperature": 0.7
}
// 转换后请求
{
"model": "gpt-5.2-codex",
"input": [...], // messages 转换为 input 格式
"stream": true, // 强制 stream=true
"store": false, // 添加 store=false
"instructions": "...", // 添加 Codex 系统提示
"reasoning": {
"effort": "high",
"summary": "auto"
},
"text": {
"verbosity": "medium"
},
"include": ["reasoning.encrypted_content"]
}
```
**转换步骤:**
1. 模型名称标准化(使用 model-map
2. `messages` → `input` 格式转换
3. 过滤 `item_reference` 和 IDs
4. 添加 `store: false`, `stream: true`
5. 添加 Codex 系统提示(从内置 prompts 加载)
6. 配置 reasoning 参数(根据模型类型)
7. 配置 text verbosity
8. 添加 include 参数
9. 移除不支持的参数(`max_output_tokens`, `max_completion_tokens`
#### 2.2 模型映射 (`model-map.ts`)
**支持的模型:**
```typescript
const MODEL_MAP: Record<string, string> = {
// GPT-5.1
"gpt-5.1": "gpt-5.1",
"gpt-5.1-none": "gpt-5.1",
"gpt-5.1-low": "gpt-5.1",
"gpt-5.1-medium": "gpt-5.1",
"gpt-5.1-high": "gpt-5.1",
// GPT-5.2
"gpt-5.2": "gpt-5.2",
"gpt-5.2-none": "gpt-5.2",
"gpt-5.2-low": "gpt-5.2",
"gpt-5.2-medium": "gpt-5.2",
"gpt-5.2-high": "gpt-5.2",
"gpt-5.2-xhigh": "gpt-5.2",
// GPT-5.1 Codex
"gpt-5.1-codex": "gpt-5.1-codex",
"gpt-5.1-codex-low": "gpt-5.1-codex",
"gpt-5.1-codex-medium": "gpt-5.1-codex",
"gpt-5.1-codex-high": "gpt-5.1-codex",
// GPT-5.1 Codex Max
"gpt-5.1-codex-max": "gpt-5.1-codex-max",
"gpt-5.1-codex-max-low": "gpt-5.1-codex-max",
"gpt-5.1-codex-max-medium": "gpt-5.1-codex-max",
"gpt-5.1-codex-max-high": "gpt-5.1-codex-max",
"gpt-5.1-codex-max-xhigh": "gpt-5.1-codex-max",
// GPT-5.1 Codex Mini
"gpt-5.1-codex-mini": "gpt-5.1-codex-mini",
"gpt-5.1-codex-mini-medium": "gpt-5.1-codex-mini",
"gpt-5.1-codex-mini-high": "gpt-5.1-codex-mini",
// GPT-5.2 Codex
"gpt-5.2-codex": "gpt-5.2-codex",
"gpt-5.2-codex-low": "gpt-5.2-codex",
"gpt-5.2-codex-medium": "gpt-5.2-codex",
"gpt-5.2-codex-high": "gpt-5.2-codex",
"gpt-5.2-codex-xhigh": "gpt-5.2-codex"
};
```
#### 2.3 Header 生成 (`headers.ts`)
**Headers 列表:**
```typescript
{
"Authorization": `Bearer ${accessToken}`,
"chatgpt-account-id": accountId,
"OpenAI-Beta": "responses=experimental",
"originator": "codex_cli_rs",
"session_id": promptCacheKey,
"conversation_id": promptCacheKey,
"accept": "text/event-stream",
"content-type": "application/json"
}
```
#### 2.4 Reasoning 配置 (`reasoning.ts`)
**Reasoning effort 配置:**
- `gpt-5.2`, `gpt-5.1`: 支持 `none/low/medium/high`(默认 `none`
- `gpt-5.2-codex`, `gpt-5.1-codex-max`: 支持 `low/medium/high/xhigh`(默认 `high`
- `gpt-5.1-codex`: 支持 `low/medium/high`(默认 `medium`
- `gpt-5.1-codex-mini`: 支持 `medium/high`(默认 `medium`
#### 2.5 请求验证 (`validator.ts`)
**验证内容:**
- `model` 参数是否存在且有效
- `messages` 或 `input` 是否存在
- 消息格式是否正确
- 必需字段是否存在
---
### 3. 响应处理模块 (`src/response/`)
#### 3.1 响应处理器 (`handler.ts`)
**处理流程:**
```typescript
async function handleResponse(response: Response, isStreaming: boolean): Promise<Response> {
if (isStreaming) {
// 流式响应:直接转发 SSE
return forwardStream(response);
} else {
// 非流式:解析 SSE 并转换为 JSON
return parseSseToJson(response);
}
}
```
#### 3.2 SSE 解析器 (`sse-parser.ts`)
**解析逻辑:**
```typescript
function parseSseStream(sseText: string): Response | null {
const lines = sseText.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.substring(6));
if (data.type === 'response.done' || data.type === 'response.completed') {
return data.response;
}
}
}
return null;
}
```
#### 3.3 响应格式转换 (`converter.ts` & `chat-completions.ts`)
**ChatGPT Responses API 格式 → OpenAI Chat Completions 格式:**
**原始格式ChatGPT**
```json
{
"type": "response.done",
"response": {
"id": "resp_...",
"status": "completed",
"output": [
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "Hello, world!"
}
]
}
],
"usage": {
"input_tokens": 100,
"output_tokens": 50,
"total_tokens": 150
}
}
}
```
**转换后格式OpenAI**
```json
{
"id": "resp_...",
"object": "chat.completion",
"created": 1736153600,
"model": "gpt-5.2-codex",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello, world!"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 100,
"completion_tokens": 50,
"total_tokens": 150
}
}
```
**流式响应格式转换:**
**原始 SSE 事件ChatGPT**
```
data: {"type": "response.output_item.add.delta", "delta": {"content": [{"type": "output_text", "text": "Hello"}]}}
data: {"type": "response.output_item.add.delta", "delta": {"content": [{"type": "output_text", "text": ", world!"}]}}
data: {"type": "response.done", "response": {...}}
```
**转换后 SSE 事件OpenAI**
```
data: {"id": "...", "object": "chat.completion.chunk", "created": 1736153600, "model": "gpt-5.2-codex", "choices": [{"index": 0, "delta": {"content": "Hello"}, "finish_reason": null}]}
data: {"id": "...", "object": "chat.completion.chunk", "created": 1736153600, "model": "gpt-5.2-codex", "choices": [{"index": 0, "delta": {"content": ", world!"}, "finish_reason": null}]}
data: {"id": "...", "object": "chat.completion.chunk", "created": 1736153600, "model": "gpt-5.2-codex", "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}]}
```
---
### 4. 内置 Prompts (`src/prompts/`)
**文件结构:**
- `gpt-5-1.md` - GPT-5.1 通用模型系统提示
- `gpt-5-2.md` - GPT-5.2 通用模型系统提示
- `gpt-5-1-codex.md` - GPT-5.1 Codex 系统提示
- `gpt-5-1-codex-max.md` - GPT-5.1 Codex Max 系统提示
- `gpt-5-1-codex-mini.md` - GPT-5.1 Codex Mini 系统提示
- `gpt-5-2-codex.md` - GPT-5.2 Codex 系统提示
**Prompt 加载器 (`index.ts`)**
```typescript
export function getPrompt(modelFamily: ModelFamily): string {
const promptFile = PROMPT_FILES[modelFamily];
return readFileSync(join(__dirname, promptFile), 'utf-8');
}
```
**Prompts 内容:** 需要从 Codex CLI GitHub 仓库下载最新的 prompts 并嵌入到项目中。
---
### 5. 日志系统 (`src/logger.ts`)
**日志级别:**
- `ERROR` - 错误日志(始终记录)
- `WARN` - 警告日志(始终记录)
- `INFO` - 信息日志(通过 `LOG_LEVEL=info` 启用)
- `DEBUG` - 调试日志(通过 `LOG_LEVEL=debug` 启用)
**日志配置:**
- 日志目录:`logs/`
- 日志文件格式:`{date}-{level}.log`
- 日志格式:`[timestamp] [level] [request-id] message`
- 控制台输出:带颜色和结构化数据
**日志内容:**
- 请求开始/结束
- Token 刷新
- 请求转换前后
- 响应状态
- 错误详情(请求/响应体)
**示例日志:**
```
[2025-01-06 10:30:00] [INFO] [req-001] POST /v1/chat/completions
[2025-01-06 10:30:00] [DEBUG] [req-001] Before transform: {"model": "gpt-5.2-codex", "messages": [...]}
[2025-01-06 10:30:00] [DEBUG] [req-001] After transform: {"model": "gpt-5.2-codex", "input": [...], "stream": true, "store": false}
[2025-01-06 10:30:01] [INFO] [req-001] Response: 200 OK, 150 tokens
[2025-01-06 10:30:01] [INFO] [req-001] Request completed in 1234ms
```
---
### 6. API 端点
#### 6.1 `POST /v1/chat/completions`
**请求:** OpenAI Chat Completions 格式
**响应:** OpenAI Chat Completions 格式(转换后)
**流程:**
1. 验证请求体
2. 检查 token自动刷新
3. 转换请求体messages → input
4. 生成 Headers
5. 转发到 `https://chatgpt.com/backend-api/codex/responses`
6. 处理响应(流式/非流式)
7. 转换响应格式ChatGPT → OpenAI
#### 6.2 `POST /v1/responses`
**请求:** OpenAI Responses API 格式
**响应:** OpenAI Responses API 格式(部分转换)
**流程:**
1. 验证请求体
2. 检查并刷新 token
3. 转换请求体(添加必需字段)
4. 生成 Headers
5. 转发到 `https://chatgpt.com/backend-api/codex/responses`
6. 处理响应(流式/非流式)
7. 返回转换后的 Responses API 格式
#### 6.3 `POST /auth/login`
**请求:** 无需参数
**响应:** 授权 URL 和说明
**流程:**
1. 生成 PKCE challenge 和 state
2. 构建授权 URL
3. 启动本地 OAuth 服务器
4. 尝试打开浏览器
5. 返回授权信息
#### 6.4 `POST /auth/callback`
**内部端点:** 仅用于本地 OAuth 服务器
- 接收 authorization code
- 验证 state
- 交换 tokens
- 保存到 `data/tokens.json`
- 返回成功页面
---
### 7. 配置管理 (`src/config.ts`)
**配置文件:** `~/.chatgpt-codex-router/config.json`
**默认配置:**
```json
{
"server": {
"port": 3000,
"host": "0.0.0.0"
},
"oauth": {
"clientId": "app_EMoamEEZ73f0CkXaXp7hrann",
"redirectUri": "http://localhost:1455/auth/callback",
"localServerPort": 1455
},
"backend": {
"url": "https://chatgpt.com/backend-api",
"timeout": 120000
},
"logging": {
"level": "info",
"dir": "logs",
"enableRequestLogging": false
},
"codex": {
"mode": true,
"defaultReasoningEffort": "medium",
"defaultTextVerbosity": "medium"
}
}
```
**环境变量:**
- `PORT` - 服务器端口(默认 3000
- `CONFIG_PATH` - 配置文件路径
- `LOG_LEVEL` - 日志级别error/warn/info/debug
- `ENABLE_REQUEST_LOGGING` - 启用请求日志true/false
---
### 8. Docker 支持 (`docker/`)
#### 8.1 Dockerfile
```dockerfile
FROM node:20-alpine
WORKDIR /app
# 安装依赖
COPY package.json package-lock.json ./
RUN npm ci --only=production
# 复制源代码
COPY src/ ./src/
COPY public/ ./public/
COPY tsconfig.json ./
# 构建项目
RUN npm run build
# 创建数据和日志目录
RUN mkdir -p /app/data /app/logs
# 暴露端口
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
# 启动服务
CMD ["npm", "start"]
```
#### 8.2 docker-compose.yml
```yaml
version: '3.8'
services:
chatgpt-codex-router:
build: .
container_name: chatgpt-codex-router
ports:
- "3000:3000"
- "1455:1455"
volumes:
- ./data:/app/data
- ./logs:/app/logs
- ./config.json:/app/.chatgpt-codex-router/config.json:ro
environment:
- PORT=3000
- LOG_LEVEL=info
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 5s
```
#### 8.3 .dockerignore
```
node_modules
npm-debug.log
.git
.gitignore
README.md
.dockerignore
Dockerfile
docker-compose.yml
test
vitest.config.ts
logs/*
data/*
!data/.gitkeep
```
---
## 开发步骤
### 阶段 1项目初始化第 1-2 天)
- [ ] 创建项目结构
- [ ] 配置 TypeScript
- [ ] 配置 package.json依赖、脚本
- [ ] 创建 .gitignore
- [ ] 设置开发环境ESLint、Prettier
### 阶段 2基础设施第 3-4 天)
- [ ] 实现日志系统 (`logger.ts`)
- [ ] 实现配置管理 (`config.ts`)
- [ ] 实现常量定义 (`constants.ts`)
- [ ] 实现类型定义 (`types.ts`)
- [ ] 创建基础服务器框架 (`server.ts`, `index.ts`)
### 阶段 3认证模块第 5-7 天)
- [ ] 实现 OAuth 流程 (`oauth.ts`)
- [ ] 实现 Token 存储 (`token-storage.ts`)
- [ ] 实现 Token 刷新 (`token-refresh.ts`)
- [ ] 实现本地 OAuth 服务器 (`server.ts`)
- [ ] 实现浏览器工具 (`browser.ts`)
- [ ] 创建 OAuth 成功页面 (`public/oauth-success.html`)
### 阶段 4请求处理第 8-10 天)
- [ ] 实现模型映射 (`model-map.ts`)
- [ ] 实现 Reasoning 配置 (`reasoning.ts`)
- [ ] 实现请求体转换 (`transformer.ts`)
- [ ] 实现 Header 生成 (`headers.ts`)
- [ ] 实现请求验证 (`validator.ts`)
- [ ] 从 GitHub 下载并内置 Codex prompts
### 阶段 5响应处理第 11-13 天)
- [ ] 实现 SSE 解析器 (`sse-parser.ts`)
- [ ] 实现响应格式转换 (`converter.ts`)
- [ ] 实现 Chat Completions 格式转换器 (`chat-completions.ts`)
- [ ] 实现响应处理器 (`handler.ts`)
- [ ] 实现流式/非流式响应处理
### 阶段 6API 端点(第 14-16 天)
- [ ] 实现 `/v1/chat/completions`
- [ ] 实现 `/v1/responses`
- [ ] 实现 `/auth/login`
- [ ] 实现 `/health` 健康检查端点
- [ ] 测试所有端点
### 阶段 7Docker 支持(第 17-18 天)
- [ ] 创建 Dockerfile
- [ ] 创建 docker-compose.yml
- [ ] 创建 .dockerignore
- [ ] 测试 Docker 构建
- [ ] 编写 Docker 使用文档
### 阶段 8测试和优化第 19-21 天)
- [ ] 单元测试
- [ ] 集成测试
- [ ] 性能测试
- [ ] 日志优化
- [ ] 错误处理优化
### 阶段 9文档和发布第 22-23 天)
- [ ] 编写 README
- [ ] 编写 API 文档
- [ ] 编写 Docker 文档
- [ ] 准备 NPM 发布
---
## 依赖项
### 生产依赖
```json
{
"hono": "^4.10.4",
"@openauthjs/openauth": "^0.4.3",
"dotenv": "^16.4.5"
}
```
### 开发依赖
```json
{
"@types/node": "^24.6.2",
"typescript": "^5.9.3",
"vitest": "^3.2.4",
"eslint": "^9.15.0",
"prettier": "^3.4.2",
"@typescript-eslint/eslint-plugin": "^8.15.0"
}
```
---
## 关键实现细节
### 1. Messages → Input 转换
**OpenAI Chat Completions 格式:**
```json
{
"messages": [
{"role": "user", "content": "Hello"}
]
}
```
**ChatGPT Responses API 格式:**
```json
{
"input": [
{"type": "message", "role": "user", "content": [{"type": "input_text", "text": "Hello"}]}
]
}
```
**转换逻辑:**
```typescript
function messagesToInput(messages: Message[]): InputItem[] {
return messages.map(msg => ({
type: "message",
role: msg.role,
content: Array.isArray(msg.content)
? msg.content.map(c => ({ type: "input_text", text: c.text }))
: [{ type: "input_text", text: msg.content }]
}));
}
```
### 2. 流式 SSE 转换
**转换逻辑:**
```typescript
async function transformSseStream(
reader: ReadableStreamDefaultReader,
model: string
): ReadableStream {
const encoder = new TextEncoder();
return new ReadableStream({
async start(controller) {
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.substring(6));
const transformed = transformChunk(data, model);
controller.enqueue(encoder.encode(`data: ${JSON.stringify(transformed)}\n\n`));
}
}
}
controller.close();
}
});
}
```
### 3. Token 刷新时机
**刷新策略:**
- 提前 5 分钟刷新(`expires_at - 300000 < Date.now()`
- 每次请求前检查
- 刷新失败时返回 401
- 刷新成功后更新本地存储
---
## 测试计划
### 单元测试
- [ ] OAuth 流程测试
- [ ] Token 存储测试
- [ ] 请求转换测试
- [ ] 响应转换测试
- [ ] SSE 解析测试
### 集成测试
- [ ] 完整 OAuth 流程测试
- [ ] Chat Completions 端点测试(流式/非流式)
- [ ] Responses API 端点测试(流式/非流式)
- [ ] Token 自动刷新测试
- [ ] 错误处理测试
### 手动测试
- [ ] 使用 curl 测试所有端点
- [ ] 使用 OpenAI SDK 测试兼容性
- [ ] 使用不同模型测试
- [ ] Docker 部署测试