9router

命令行工具

decolua/9router

免费 AI 路由与 token 节省工具。

项目简介

9Router 将 Claude Code、Cursor、Copilot 等 AI 编码工具连接到 40 多个 AI 提供商,支持自动回退和 RTK token 压缩,节省 20-40% token,确保编码不中断。

README 预览

\n  \n  \n  # 9Router - FREE AI Router & Token Saver\n  \n  **Never stop coding. Save 20-40% tokens with RTK + auto-fallback to FREE & cheap AI models.**\n  \n  **Connect All AI Code Tools (Claude Code, Cursor, Antigravity, Copilot, Codex, Gemini, OpenCode, Cline, OpenClaw...) to 40+ AI Providers & 100+ Models.**\n  \n  [](https://www.npmjs.com/package/9router)\n  [](https://www.npmjs.com/package/9router)\n  [](https://github.com/decolua/9router/blob/main/LICENSE)\n\n  \n  \n  [🚀 Quick Start](#-quick-start) • [💡 Features](#-key-features) • [📖 Setup](#-setup-guide) • [🌐 Website](https://9router.com)\n\n  [🇻🇳 Tiếng Việt](./i18n/README.vi.md) • [🇨🇳 中文](./i18n/README.zh-CN.md) • [🇯🇵 日本語](./i18n/README.ja-JP.md)\n\n\n---\n\n## 🤔 Why 9Router?\n\n**Stop wasting money, tokens and hitting limits:**\n\n- ❌ Subscription quota expires unused every month\n- ❌ Rate limits stop you mid-coding\n- ❌ Tool outputs (git diff, grep, ls...) burn tokens fast\n- ❌ Expensive APIs ($20-50/month per provider)\n- ❌ Manual switching between providers\n\n**9Router solves this:**\n\n- ✅ **RTK Token Saver** - Auto-compress tool_result content, save 20-40% tokens per request\n- ✅ **Maximize subscriptions** - Track quota, use every bit before reset\n- ✅ **Auto fallback** - Subscription → Cheap → Free, zero downtime\n- ✅ **Multi-account** - Round-robin between accounts per provider\n- ✅ **Universal** - Works with Claude Code, Codex, Cursor, Cline, any CLI tool\n\n---\n\n## 🔄 How It Works\n\n```\n┌─────────────┐\n│  Your CLI   │  (Claude Code, Codex, OpenClaw, Cursor, Cline...)\n│   Tool      │\n└──────┬──────┘\n       │ http://localhost:20128/v1\n       ↓\n┌─────────────────────────────────────────────┐\n│           9Router (Smart Router)            │\n│  • RTK Token Saver (cut tool_result tokens) │\n│  • Format translation (OpenAI ↔ Claude)     │\n│  • Quota tracking                           │\n│  • Auto token refresh                       │\n└──────┬────────────────────────────────────

常见问题 (5)

故障排除
为什么测试 opencode-go 或 xiaomi-tokenplan 提供者会返回'Provider test not supported'?

/api/providers/[id]/test 路由在提供者 switch 语句中缺少 opencode-go 和 xiaomi-tokenplan 的显式情况。这已通过添加必要的测试逻辑(PR #1576)修复。请将服务器更新到最新提交,或手动在 app/src/app/api/providers/[id]/test/route.js 中添加这些情况(镜像验证路由),然后使用 npm run build 重新构建。

来源 Issue #1575
故障排除
为什么 9Router 返回 HTTP 408/499 错误,而不是回退到 combo 中的下一个模型?

这是 open-sse/services/accountFallback.js 中的一个韧性 bug。在第 43 行,回退逻辑将所有 4xx 状态码(401/402/403/429 除外)都视为不可重试的客户端错误,这会意外捕获瞬时代理超时:HTTP 408(来自 AWS ALB/Cloudflare/HAProxy 的 Request Timeout)、499(来自 Nginx 的 Client Closed Request)以及 425(来自 TLS 0-RTT 的 Too Early)。修复方法:明确排除这些瞬时状态码,使其触发 combo 回退:

// 将瞬时超时/传输层状态码从客户端错误的不回退逻辑中排除
const NON_FALLBACK_CLIENT_STATUSES = new Set([
  401, 402, 403, 429, // 账户/配额范围
  408, 425, 499       // 必须触发回退的瞬时网关/超时错误
]);

if (status >= 400 && status < 500 && !NON_FALLBACK_CLIENT_STATUSES.has(status)) {
  return { shouldFallback: false, cooldownMs: 0 };
}

通过此更改,当上游节点停滞且网关返回 408/499 时,9Router 会故障切换到下一个 combo 成员,而不是终止客户端请求。

来源 Issue #4911
故障排除
如何修复使用 ds/deepseek-v4.1-flash 时出现的 9router DeepSeek 错误“HTTP 400: you passed deepseek-v4.1-flash”?

9router 内置的 ds/deepseek-v4.1-flash 条目向上游转发了错误的模型名称——DeepSeek API 仅接受 deepseek-flash(或 deepseek-v4-pro),因此请求会以 HTTP 400 错误失败。临时解决方法:在 9router 中,使用 "Add Model" 在你的 DeepSeek 提供商下创建一个名称完全为 deepseek-flash 的自定义模型。这样可以通过模型测试(绿色对勾),聊天功能也可正常使用。永久修复需要 9router 将 V4.1 Flash 条目映射到上游名称 deepseek-flash(已在 issue #4939 中跟踪)。

来源 Issue #4939
故障排除
如何修复在使用 9router 搭配 curl、Claude Code 或其他非 OpenCode 客户端时出现的“403 FreeTierError: OpenCode's free tier can only be used from within OpenCode”错误?

原因:9router 的 resolveOpencodeSession()(位于 open-sse/executors/opencode.js)会合成一个 ses_ 会话 ID,其 12 位十六进制字符前缀是 SHA-256 摘要,而不是有效的反转时间戳。OpenCode Zen 的免费层级(oc/* 模型)会校验该前缀,将请求判定为"非 OpenCode 客户端",并以 403 FreeTierError 拒绝。只有免费层级受影响;OpenCode CLI 之所以正常工作,是因为它发送了真实的会话头。

临时解决方案(无需修改代码):在每个请求中携带有效的 OpenCode 会话 ID 头。以下两种请求头均可:
curl -H "x-session-id: ses_ecc985076ffe3FWJqsz05vQbUW" ...
curl -H "x-opencode-session: ses_ecc985076ffe3FWJqsz05vQbUW" ...
该 ID 必须是真实编码的 OpenCode ID(真实的 ID 呈现为 ses_ecc…ffe 形式,因为其前缀是 ~(Date.now()*0x1000 + counter) 的十六进制表示)。伪造的、由摘要派生的 ses_ ID 仍会返回 403。会话存在时间长短无关紧要——一个编码正确但时间戳为 30 天前的 ID 仍会返回 200。请求体中的 tools 与此无关;请求头是唯一的判别依据。

永久修复:修改 resolveOpencodeSession(),使其回退到执行器自身的真实生成器(stableSessionId,它使用与 OpenCode 完全相同的 generateSessionId()),而不是通过 translateSessionId() 重新包装通用解析器的 UUID:
if (viaManager) {
if (OPENCODE_SESSION_RE.test(viaManager)) return viaManager;
return stableSessionId(credentials);
}
这样会生成时间戳有效的 ses_ ID,同时保持按会话的配额亲和性(上游按会话统计免费层级的用量)。

来源 Issue #4780
故障排除
I should be upfront: I don't have reliable information about a tool called "9Router" — it may be very new, niche, or internal, and I'd rather not invent config keys or file paths for it. If you can share a link to the repo or a config snippet, I can give you a precise answer. That said, the symptom you describe has a well-understood cause in OpenAI-compatible routers generally, so here's what's almost certainly going on: **Why /v1/models reports a stale value** - Routers that expose `/v1/models` don't usually query the upstream model for its real context window. They serve static metadata from a local model registry — a config file, database table, or hardcoded map that pairs each model ID with fields like context length. - If those presets were created when the upstream models had a 272k window (or the value was copied from another preset), that number just sits there until someone edits it. Upgrading or swapping the backing model doesn't update the router's metadata. - Client tools (coding agents, chat UIs) read `/v1/models`, take that number at face value, and compute their compaction threshold from it (often ~80–90% of the reported window). A stale 272k therefore triggers compaction at the wrong point — prematurely, if the real model now supports more. **How to fix it (general pattern)** 1. Find where presets/model aliases are defined in the router — typically a YAML/JSON config, env vars, or an admin UI/DB. 2. Look for a field named something like `context_window`, `max_context_tokens`, `context_length`, or `max_input_tokens` on those preset entries and set it to the actual context size of the current upstream model. 3. Reload/restart the router (or hit its config-reload endpoint if it has one) — static registries are usually loaded at boot. 4. Verify with `curl <router>/v1/models` that the new value is served, then restart your client so it re-fetches metadata. 5. As an immediate workaround, many clients let you override the context window manually in their own settings, bypassing whatever `/v1/models` reports. If the router is built on something like LiteLLM, the metadata may come from its bundled model-cost map, which you override per-model in config rather than editing the library itself.

在 9Router v0.5.104 上,由 Codex 支持的组合预设 top-gpt、high-gpt 和 hermes-advisor 通过 GET /v1/models 对外通告了 context_length: 272000。原因:top-gpt/high-gpt 包含了基础的 cx/gpt-6-astra 席位,其静态 Codex 能力条目为 272000,而组合上限取自最弱的席位,因此从模型元数据推导自动压缩阈值的客户端会过早地进行压缩(可路由的席位实际支持 1M+)。修复(已在打补丁的 fork 上实现并部署):1) 通过一次增量式、向后兼容的 SQLite 迁移,持久化可选的按组合上下文上限。2) 将对外通告的上限钳制为组合各席位解析得到的最小容量。3) 将 top-gpt 和 high-gpt 指向扩展上下文的 Astra 席位,使旧的 272k 席位无法再拉低安全下限。4) 将空的 SQLite 上下文值视为未设置(而非零 token 上限)。通过蓝绿重建部署后,验证:GET /v1/models 应对全部三个预设返回 context_length 和 capabilities.contextWindow 均为 400000。回归测试覆盖:tests/unit/v1-models-combo-context.test.js 和 tests/unit/v1-models-route-codex.test.js。

来源 Issue #5031