# 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 = { // 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 { 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`) - [ ] 实现流式/非流式响应处理 ### 阶段 6:API 端点(第 14-16 天) - [ ] 实现 `/v1/chat/completions` - [ ] 实现 `/v1/responses` - [ ] 实现 `/auth/login` - [ ] 实现 `/health` 健康检查端点 - [ ] 测试所有端点 ### 阶段 7:Docker 支持(第 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 部署测试