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

21 KiB
Raw Blame History

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

关键常量:

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
  • 存储格式:
    {
      "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)

核心转换逻辑:

// 原始请求
{
  "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. messagesinput 格式转换
  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)

支持的模型:

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 列表:

{
  "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 参数是否存在且有效
  • messagesinput 是否存在
  • 消息格式是否正确
  • 必需字段是否存在

3. 响应处理模块 (src/response/)

3.1 响应处理器 (handler.ts)

处理流程:

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)

解析逻辑:

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

{
  "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

{
  "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)

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

默认配置:

{
  "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

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

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 发布

依赖项

生产依赖

{
  "hono": "^4.10.4",
  "@openauthjs/openauth": "^0.4.3",
  "dotenv": "^16.4.5"
}

开发依赖

{
  "@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 格式:

{
  "messages": [
    {"role": "user", "content": "Hello"}
  ]
}

ChatGPT Responses API 格式:

{
  "input": [
    {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "Hello"}]}
  ]
}

转换逻辑:

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 转换

转换逻辑:

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 部署测试