不只是 API 代理:用 CliGate 把 Claude Code、Codex CLI 和 Gemini CLI 收到一个本地网关里

6 阅读10分钟

摘要

很多团队开始同时使用 Claude Code、Codex CLI、Gemini CLI、OpenClaw 这类 AI 编程工具后,会遇到一个很现实的问题:每个工具都有自己的协议、配置文件、凭证来源和模型命名方式。工具越多,配置越散;账户、API Key、本地模型和日志也越难统一管理。

CliGate 的思路不是再做一个远端中转服务,而是在本机启动一个 localhost:8081 的本地 AI gateway:上游可以是 ChatGPT / Claude 账户池、各类 provider API Key,也可以是本地运行时;下游则统一接住 Claude Code、Codex CLI、Gemini CLI、OpenClaw、Web Chat 和渠道会话。

这篇文章不写安装流水账,而是结合项目源码和文档,拆一下 CliGate 里比较有意思的几个工程点:协议转换、应用识别、凭证路由、能力感知降级,以及为什么它和普通 API proxy 的侧重点不太一样。

1. 问题不是“能不能代理”,而是“能不能稳定地接住多个客户端”

单个 OpenAI-compatible 代理并不复杂:客户端打到 /v1/chat/completions,服务端转发到上游,再把响应返回即可。

但 AI 编程 CLI 的问题更碎:

  • Claude Code 和 OpenClaw 常走 Anthropic Messages:POST /v1/messages
  • Codex CLI 会走 OpenAI Responses 或它自己的内部接口:POST /v1/responsesPOST /backend-api/codex/responses
  • Gemini CLI 使用 Gemini API 风格:POST /v1beta/models/*
  • 不同客户端对 tool call、流式事件、图片输入、文件输入、reasoning/thinking 的表达都不一样
  • 账户池、API Key 池和本地模型都可能是可用上游,但可支持能力不同

CliGate 的 README_CN 和 docs/ARCHITECTURE.md 里把定位说得比较直接:它是一个本地控制平面,而不是单一路由转发器。对应到代码里,主要入口分布在:

  • src/routes/messages-route.js:Anthropic Messages 入口,服务 Claude Code / OpenClaw 等
  • src/routes/responses-route.jssrc/routes/codex-route.js:OpenAI Responses / Codex 相关入口
  • src/routes/gemini-api-route.js:Gemini CLI 入口
  • src/app-routing.js:识别请求来源,并处理应用级绑定
  • src/translators/:协议转换与 normalizer
  • src/api-key-manager.jssrc/account-rotation/:API Key 与账户池调度

换句话说,CliGate 要解决的是“多个 AI 编程客户端如何共用一套本地能力面”的问题。

2. 北向协议统一:先把客户端差异收进来

从 API 面看,CliGate 暴露了几组关键入口:

入口典型客户端说明
POST /v1/messagesClaude Code / OpenClawAnthropic Messages 兼容入口
POST /v1/chat/completionsOpenAI-compatible 客户端Chat Completions 兼容入口
POST /v1/responsesCodex CLIOpenAI Responses 兼容入口
POST /backend-api/codex/responsesCodex CLICodex 内部兼容入口
POST /v1beta/models/*Gemini CLIGemini API 兼容入口

这层的关键不是“端点多”,而是每个入口后面都要保留客户端语义。

/v1/messages 为例,Claude Code 请求里可能包含:

  • system
  • messages
  • tools
  • tool_choice
  • stream
  • 图片 / 文件 content block
  • thinking block 或 signature

如果上游刚好是 Anthropic 或 Claude 账户,可以接近直通;但如果上游是 ChatGPT account、OpenAI Responses、Azure OpenAI、Gemini、Vertex AI,就必须转换。

CliGate 在 src/translators/request/anthropic-to-openai-responses.js 中把 Anthropic Messages 转成 OpenAI Responses:

  • system 会被提取成 instructions
  • Anthropic message content 会被规范化成 Responses input
  • Anthropic tools 会转成 Responses tools
  • max_tokenstemperaturetop_pstop_sequencesmetadatauser 等参数会按 provider 能力保留
  • 图片、文件、tool_result 富内容会交给 normalizer 处理

项目没有把这些逻辑塞在一个巨大 if/else 里,而是拆到了 normalizers,例如:

  • normalizers/anthropic-messages.js
  • normalizers/multimodal.js
  • normalizers/tools.js
  • normalizers/responses-request.js
  • normalizers/responses-events.js

这对 AI CLI 很重要。工具调用、流式增量、reasoning/thinking 的兼容问题通常不是“字段名改一下”能解决的,必须有一层专门处理语义保真和可接受降级。

3. 南向不是单一 provider:账户池、API Key、本地模型都可以成为上游

CliGate 的另一个特点是,上游不是单一 API Key。

根据 README_CN、docs/product-manual.zh-CN.md 和源码,当前可管理的上游大致包括:

  • ChatGPT 账户池
  • Claude 账户池
  • Antigravity 账户池
  • OpenAI / Azure OpenAI / Anthropic / Gemini / Vertex AI / MiniMax / Moonshot / ZhipuAI / DeepSeek 等 API Key
  • 可选本地运行时,例如 Ollama
  • Kilo free route 一类免费模型路径

src/api-key-manager.js 里,API Key 会按 provider 类型实例化成不同 provider class,并维护启用状态、请求数、错误数、token 与成本统计。选择 Key 时不是固定拿第一条,而是从可用 Key 中按请求数做简单负载均衡。

账户池则由 src/account-rotation/ 管理,支持 sequential / random 等策略,并维护 rate limit、invalid 状态和冷却时间。对于 Claude 账户,messages-route.js 中还能看到模型维度的 cooldown:某个账号在某个模型上被限流后,不会马上继续打同一个模型,而是先让后续请求尝试其他账户或其他上游。

这类设计的意义在于:开发工具侧只看到一个本地入口,实际请求可以按策略落到不同凭证来源上。

4. 应用级路由:不是所有客户端都应该走同一个凭证

很多代理工具只有“账户优先”或“Key 优先”这种全局策略。CliGate 进一步做了 app-assigned routing。

src/app-routing.js 里定义了几类 app id:

  • codex
  • claude-code
  • gemini-cli
  • openclaw
  • unknown-openai-client
  • unknown-anthropic-client

请求来源主要通过 path、header、User-Agent 和约定 token 来识别。例如:

  • /backend-api/codex/*/v1/responses 识别为 Codex
  • /v1beta/models/* 识别为 Gemini CLI
  • /v1/messages 下通过 x-proxypool-client、User-Agent 或代理 token 区分 Claude Code / OpenClaw

docs/APP_ROUTING.md 的设计里,路由模式分成两种:

  • automatic:保持默认自动路由逻辑
  • app-assigned:先看应用是否绑定了指定凭证,如果绑定不可用,再根据配置决定是否 fallback 到默认逻辑

这带来的体验差异很明显:

  • Codex 可以固定走某个 ChatGPT 账户
  • Claude Code 可以固定走某个 Claude 账户或 Anthropic Key
  • OpenClaw 可以走另一个 provider key
  • Gemini CLI 可以绑定 Gemini / Vertex / 本地模型

对个人开发者来说,这是“少改配置”;对小团队来说,这是“不同工具、不同用途、不同成本来源”可以被表达出来。

5. 能力感知路由:会根据请求内容挑更合适的 provider

比较值得展开的是 /v1/messages 的兼容 provider 排序。

src/translators/request-features.js 中,CliGate 会先分析 Anthropic 请求特征:

  • 是否包含图片输入
  • 是否包含文件输入
  • tool_result 是否包含图片、文件等结构化内容
  • 是否使用 hosted tools

然后在 src/translators/provider-capabilities.js 中为 provider 打分:

  • 原生 Anthropic 支持 hosted tools、图片、文件、结构化 tool_result,分数更高
  • OpenAI / Azure OpenAI / DeepSeek 走 Responses bridge,支持能力取决于 capability profile
  • Gemini / Vertex 有自己的多模态与工具限制
  • Vertex Claude rawPredict 这类链路对 hosted tools 更合适

这不是为了“绝对智能”,而是避免明显不兼容的 bridge 被排在最前面。比如一个请求带了 web_search 这类 hosted tool,普通 OpenAI bridge 并不能完整承接;能力感知排序会让更合适的 provider 先尝试。

项目还提供了 strictTranslatorCompatibility。默认情况下,一些转换降级会继续兼容返回并在 header / 日志里记录;开启严格模式后,工具降级、tool_choice 降级可以直接变成 400 invalid_request_error

这个开关很实用:个人使用时可以“尽量跑通”,生产或团队场景则可以“宁愿失败也不要静默丢语义”。

6. 多模态与 thinking:真正麻烦的是细节保真

docs/TRANSLATOR_CAPABILITY_MATRIX.md 里有一段很能说明问题:项目当前已经覆盖 Anthropic image block 到 OpenAI Responses input_image,Anthropic document/file 到 Responses input_file,以及 tool_result 中 text + image + document 混合内容的保留。

这些看起来是格式转换,实际更像“协议间语义迁移”。

举几个源码里的细节:

  1. normalizers/anthropic-messages.js 会把 assistant 的 tool_use 转成 Responses function_call,同时处理 tool id 映射。
  2. thinking / signature 不只是文本,项目会通过 thinking.js 相关逻辑做缓存和恢复,避免工具调用链断掉。
  3. providers/gemini.js 中明确处理了 Gemini 的限制:当 tool path 需要兼容时,会禁用 thinking budget;当 tool_result 带图片等多模态内容时,部分情况下会降级为 user multimodal parts。

这也是 CliGate 和普通 API proxy 的重要差异。普通 proxy 更多关心“请求能不能发出去”;CliGate 关心的是“Claude Code / Codex 这类强协议客户端在换 provider 后还能不能保持可预期行为”。

7. 可观测性:本地工具也需要日志、用量和成本

如果只是个人临时代理,请求日志可能不重要。但一旦把多个 CLI、多个账户和多个 provider 放到一起,没有观测能力就很难排查:

  • 这个请求到底从哪个客户端来?
  • 最终走了哪个账户或 API Key?
  • 模型名有没有被映射?
  • 是上游限流、凭证失效,还是协议转换降级?
  • 这段时间哪个 provider 成本更高?

CliGate 在产品层提供了 Usage、Pricing、Request Logs、Logs、API Explorer 等页面;API 文档里也有对应接口,例如:

  • /api/usage/overview
  • /api/request-logs
  • /api/pricing
  • /api/model-mappings
  • /api/agent-runtimes/sessions

这说明它的定位更接近“本地控制台”,而不是只在终端里跑一个代理进程。

8. 和同类方案的差异

如果和常见 API 代理或统一中转服务对比,CliGate 的差异大致在四点:

第一,它更偏本地优先。默认跑在 localhost:8081,文档中也强调不依赖托管中转服务。这降低了把个人 CLI 凭证、OAuth 账户和本地运行时暴露到远端的需求。

第二,它面向具体 AI 编程工具做集成,而不只是提供 OpenAI-compatible 入口。Claude Code、Codex CLI、Gemini CLI、OpenClaw 都有配置助手或专门路由。

第三,它把账户池和 API Key 池放在同一套路由层里。很多工具只处理 API Key,而 CliGate 同时考虑 ChatGPT / Claude / Antigravity OAuth 账户、provider key、本地模型和免费模型路径。

第四,它承认 provider 能力不一致,并把降级显式化。多模态、hosted tools、thinking、tool_result 这些能力不是所有上游都能等价支持,项目通过 capability matrix、provider capabilities、strict mode 和测试基线来维护这些差异。

当然,它也不是万能的。当前架构更像产品型单体:Web UI、配置助手、代理路由、账户管理都在同一个 Node.js / Express 项目里。这让开箱体验更好,但如果要作为平台底座长期扩展,后续可能还需要继续抽象 translator pipeline、认证调度和配置治理。

9. 适合什么场景

我觉得 CliGate 适合这几类使用场景:

  • 同时使用 Claude Code、Codex CLI、Gemini CLI,希望配置入口统一
  • 有多个 ChatGPT / Claude 账户,想做本地账户池与轮换
  • 同时有 API Key 和账户订阅,希望按工具或按模型路由
  • 希望在 Web UI 里看请求日志、用量、成本和模型映射
  • 想把本地模型或免费模型路径作为部分请求的 fallback
  • 想从 Telegram / 飞书等渠道继续使用 Codex / Claude Code runtime session

不太适合的场景也很明确:

  • 只需要一个极简 OpenAI-compatible 转发器
  • 希望部署成多租户、中心化、强治理的平台服务
  • 不想在本地保存任何 OAuth 账户或工具配置

小结

CliGate 最有意思的地方,不在于“又做了一个 AI API proxy”,而在于它把 AI 编程工具的几个真实痛点放到了一起处理:

  • 北向接住多种 CLI 协议
  • 中间做协议转换和 capability-aware routing
  • 南向统一账户池、API Key 池和本地模型
  • 产品层提供仪表盘、日志、用量、配置助手和渠道会话

当你只用一个工具、一个 API Key 时,这些能力可能显得有点重;但当 Claude Code、Codex CLI、Gemini CLI、OpenClaw、多个账户和多个 provider 同时出现时,一个本地控制平面的价值就很明显了。

从工程角度看,CliGate 的经验也比较值得借鉴:AI 工具链的兼容不只是“适配一个接口”,而是要认真处理协议语义、工具调用、多模态、reasoning、凭证状态和降级策略。否则表面上接通了,真正跑复杂任务时还是会在细节上掉链子。