English | 中文
A tiny Anthropic-compatible gateway for Claude Code routing experiments.
Claude Code talks only to this local server. The router forwards each /v1/messages request to a configured local route such as control or execution, then lets the assistant choose the next route by appending a JSON directive at the end of the response.
{"route":"execution","reason":"local code edit"}The router treats that directive as a whitelist state update only. Real upstream baseUrl, API keys, and model names stay in local config and are never trusted from model output.
You run tiny-router as a local gateway first. Then Claude Code connects to tiny-router instead of connecting directly to your model provider.
Claude Code -> tiny-router -> upstream control or upstream execution
The config file is local-only:
- Claude Code only sees
ANTHROPIC_BASE_URL=http://127.0.0.1:3456and your localrouterApiKey. tiny-routerreadsrouter.config.jsonand knows the real upstream API keys, base URLs, and model names.- The assistant may suggest the next route with
{"route":"control"}or{"route":"execution"}. - The router only accepts configured local route names. It never trusts model output for real API keys, real model names, or real base URLs.
You do not need to manually add the routing instruction to every prompt. By default, tiny-router appends routeInstruction to the request system prompt on each request. You can edit that instruction in router.config.json, or disable it with:
{
"injectRouteInstruction": false
}Use a stronger or more expensive model for planning, architecture, and difficult debugging, then let the conversation switch itself to a cheaper model for narrow implementation work.
This is experimental. It may reduce cost for some workflows, but it is not a guarantee.
- Node.js 18+
- Claude Code or another client that can use an Anthropic-compatible
/v1/messagesendpoint
- Create your local config:
cp router.config.example.json router.config.json- Put your real providers in
router.config.json:
{
"upstreams": {
"control": {
"baseUrl": "https://your-strong-provider.example.com",
"apiKey": "your-strong-provider-key",
"model": "strong-model"
},
"execution": {
"baseUrl": "https://your-cheap-provider.example.com",
"apiKey": "your-cheap-provider-key",
"model": "cheap-model"
}
}
}- Start the local gateway from the
tiny-routerproject root directory:
cd tiny-router
npm startYou should see output like:
tiny-router listening on http://127.0.0.1:3456
next route: control
Keep this terminal open. This process is the local gateway: Claude Code will send requests to http://127.0.0.1:3456, and tiny-router will forward them to the configured upstream provider.
- In a second terminal, start Claude Code with the gateway as its Anthropic endpoint:
ANTHROPIC_BASE_URL=http://127.0.0.1:3456 ANTHROPIC_API_KEY=local-router-key claude- Use Claude Code normally. The router will inject the routing instruction, forward each request to the current route, read the assistant's route directive, and use that route on the next request.
Use two terminals so it is clear which process is the gateway and which process is Claude Code.
Step 1: in the tiny-router project directory, start the gateway:
cd /d D:\path\to\tiny-router
start-router.cmdKeep this terminal open.
Step 2: in the project where you actually want to use Claude Code, set Claude Code to use the running gateway and then start Claude Code:
cd /d D:\path\to\your-project
set ANTHROPIC_AUTH_TOKEN=
set ANTHROPIC_BASE_URL=http://127.0.0.1:3456
set ANTHROPIC_API_KEY=local-router-key
claudeReplace local-router-key with the routerApiKey value from tiny-router\router.config.json if you changed it.
The router.config.json file belongs in the tiny-router directory by default. Other projects do not need their own router config unless you intentionally want a different gateway config and start tiny-router with TINY_ROUTER_CONFIG pointing at that file.
Copy the example config:
cp router.config.example.json router.config.jsonEdit router.config.json with your own upstreams:
{
"listen": {
"host": "127.0.0.1",
"port": 3456
},
"routerApiKey": "local-router-key",
"defaultRoute": "control",
"upstreams": {
"control": {
"baseUrl": "https://expensive-provider.example.com",
"apiKey": "your-expensive-provider-key",
"model": "expensive-model-name"
},
"execution": {
"baseUrl": "https://cheap-provider.example.com",
"apiKey": "your-cheap-provider-key",
"model": "cheap-model-name"
}
}
}You can also use Claude Code-style env blocks:
{
"upstreams": {
"control": {
"env": {
"ANTHROPIC_AUTH_TOKEN": "your-provider-key",
"ANTHROPIC_BASE_URL": "https://provider.example.com",
"ANTHROPIC_MODEL": "model-name"
}
}
}
}Start the gateway from the tiny-router project root directory:
cd tiny-router
npm startLeave that terminal running while you use Claude Code.
Point Claude Code at it:
ANTHROPIC_BASE_URL=http://127.0.0.1:3456 ANTHROPIC_API_KEY=local-router-key claudeOn Windows PowerShell:
$env:ANTHROPIC_BASE_URL="http://127.0.0.1:3456"
$env:ANTHROPIC_API_KEY="local-router-key"
claudeIf you want one gateway to serve multiple Claude Code terminals with different route configs, use clients instead of a single routerApiKey:
{
"listen": {
"host": "127.0.0.1",
"port": 3456
},
"clients": {
"project-a": {
"apiKey": "router-token-project-a",
"config": "/path/to/project-a/router.config.json"
},
"project-b": {
"apiKey": "router-token-project-b",
"config": "/path/to/project-b/router.config.json"
}
}
}Each referenced config file uses the same schema as router.config.example.json: defaultRoute, stateFile, routeInstruction, and upstreams.
When using multi-client mode, start the gateway the same way, but each Claude Code terminal uses its own token:
ANTHROPIC_BASE_URL=http://127.0.0.1:3456 ANTHROPIC_API_KEY=router-token-project-a claudeThe router token is only a local gateway credential. It is not your upstream provider API key. The real upstream API keys stay inside each project config file.
- Supports
POST /v1/messages. - Supports normal JSON responses and streaming SSE responses.
- Replaces the request
modelwith the configured model for the current route. - Appends a route instruction to the system prompt by default.
- Accepts only configured route names from the assistant directive.
routeis preferred;modelremains a backward-compatible alias. - Keeps the previous route if the directive is missing or invalid.
- Writes route state to
.router-state.jsonby default; in multi-client mode, each client gets its own state file. - Preserves upstream paths, so a base URL like
https://api.example.com/codingbecomeshttps://api.example.com/coding/v1/messages.
- Do not commit
router.config.jsonor any real API keys. - Do not expose this router to the public internet.
- Keep
listen.hostset to127.0.0.1unless you know exactly what you are doing. - The assistant can only choose configured local route names; it cannot choose a real model name, API key, or base URL.
- Rotate any key that was committed, logged publicly, or pasted into a public issue.
Run syntax checks:
npm run checkRun the fake upstream integration test:
npm testThe test starts two local fake upstreams and verifies route switching, model rewriting, invalid route fallback, streaming directive parsing, and upstream path handling.
MIT
English | 中文
一个很小的 Anthropic-compatible gateway,用来做 Claude Code 的模型路由实验。
Claude Code 只连接这个本地服务。router 会把每一轮 /v1/messages 请求转发到配置好的本地 route,比如 control 或 execution,然后让 assistant 在回复末尾追加一个 JSON 指令,用来选择下一轮 route。
{"route":"execution","reason":"local code edit"}router 只把这个 JSON 当成白名单状态更新。真实的上游 baseUrl、API key、模型名都只保存在本地配置里,永远不相信模型输出里的真实上游信息。
你需要先启动 tiny-router 这个本地 gateway。然后让 Claude Code 连接 tiny-router,而不是直接连接模型服务商。
Claude Code -> tiny-router -> 上游 control 或上游 execution
配置文件只保存在本地:
- Claude Code 只知道
ANTHROPIC_BASE_URL=http://127.0.0.1:3456和你的本地routerApiKey。 tiny-router会读取router.config.json,里面保存真实上游 API key、base URL 和模型名。- assistant 可以用
{"route":"control"}或{"route":"execution"}建议下一轮 route。 - router 只接受配置里的本地 route 名。它永远不会相信模型输出里的真实 API key、真实模型名或真实 base URL。
你不需要手动在每个 prompt 里写路由约束。默认情况下,tiny-router 每轮都会把 routeInstruction 追加到 system prompt。你可以在 router.config.json 里修改这段约束,也可以关闭它:
{
"injectRouteInstruction": false
}你可以用更强或更贵的模型处理规划、架构、复杂 debug,然后让同一个 Claude Code 会话自动切到更便宜的模型处理局部实现、跑命令、补测试等窄任务。
这是一个实验项目。它可能在某些工作流里降低成本,但不保证一定省钱。
- Node.js 18+
- Claude Code,或者其他能使用 Anthropic-compatible
/v1/messagesendpoint 的客户端
- 创建本地配置:
cp router.config.example.json router.config.json- 在
router.config.json里写入你的真实模型服务商配置:
{
"upstreams": {
"control": {
"baseUrl": "https://your-strong-provider.example.com",
"apiKey": "your-strong-provider-key",
"model": "strong-model"
},
"execution": {
"baseUrl": "https://your-cheap-provider.example.com",
"apiKey": "your-cheap-provider-key",
"model": "cheap-model"
}
}
}- 在
tiny-router项目根目录启动本地 gateway:
cd tiny-router
npm start正常情况下你会看到类似输出:
tiny-router listening on http://127.0.0.1:3456
next route: control
这个终端要保持打开。这个进程就是本地 gateway:Claude Code 会把请求发到 http://127.0.0.1:3456,然后 tiny-router 再转发到你配置的上游模型服务商。
- 再开一个终端,让 Claude Code 把这个 gateway 当成 Anthropic endpoint:
ANTHROPIC_BASE_URL=http://127.0.0.1:3456 ANTHROPIC_API_KEY=local-router-key claude- 正常使用 Claude Code。router 会自动注入路由约束,把请求转发到当前 route,读取 assistant 回复里的 route 指令,并在下一轮切换到对应 route。
建议用两个终端,这样能明确区分:哪个终端在跑 gateway,哪个终端在跑 Claude Code。
第一步:在 tiny-router 项目目录启动 gateway:
cd /d D:\path\to\tiny-router
start-router.cmd这个终端保持打开。
第二步:进入你真正想用 Claude Code 的项目目录,设置 Claude Code 连接正在运行的 gateway,然后启动 Claude Code:
cd /d D:\path\to\your-project
set ANTHROPIC_AUTH_TOKEN=
set ANTHROPIC_BASE_URL=http://127.0.0.1:3456
set ANTHROPIC_API_KEY=local-router-key
claude如果你改过 tiny-router\router.config.json 里的 routerApiKey,这里的 local-router-key 要换成你的值。
默认情况下,router.config.json 放在 tiny-router 目录里就够了。其他项目不需要各放一份 router 配置,除非你有意为不同项目使用不同 gateway 配置,并用 TINY_ROUTER_CONFIG 指向对应配置文件来启动 tiny-router。
复制配置模板:
cp router.config.example.json router.config.json编辑 router.config.json,填入你自己的上游:
{
"listen": {
"host": "127.0.0.1",
"port": 3456
},
"routerApiKey": "local-router-key",
"defaultRoute": "control",
"upstreams": {
"control": {
"baseUrl": "https://expensive-provider.example.com",
"apiKey": "your-expensive-provider-key",
"model": "expensive-model-name"
},
"execution": {
"baseUrl": "https://cheap-provider.example.com",
"apiKey": "your-cheap-provider-key",
"model": "cheap-model-name"
}
}
}也可以使用 Claude Code 风格的 env 配置:
{
"upstreams": {
"control": {
"env": {
"ANTHROPIC_AUTH_TOKEN": "your-provider-key",
"ANTHROPIC_BASE_URL": "https://provider.example.com",
"ANTHROPIC_MODEL": "model-name"
}
}
}
}在 tiny-router 项目根目录启动 gateway:
cd tiny-router
npm start使用 Claude Code 时,这个终端需要保持运行。
让 Claude Code 连接本地 router:
ANTHROPIC_BASE_URL=http://127.0.0.1:3456 ANTHROPIC_API_KEY=local-router-key claudeWindows PowerShell:
$env:ANTHROPIC_BASE_URL="http://127.0.0.1:3456"
$env:ANTHROPIC_API_KEY="local-router-key"
claude如果你想让一个 gateway 同时服务多个 Claude Code 终端,每个终端使用不同的 route 配置,可以用 clients 替代单一的 routerApiKey:
{
"listen": {
"host": "127.0.0.1",
"port": 3456
},
"clients": {
"project-a": {
"apiKey": "router-token-project-a",
"config": "/path/to/project-a/router.config.json"
},
"project-b": {
"apiKey": "router-token-project-b",
"config": "/path/to/project-b/router.config.json"
}
}
}每个引用的 config 文件使用和 router.config.example.json 相同的 schema:defaultRoute、stateFile、routeInstruction、upstreams。
使用多客户端模式时,gateway 启动方式不变,但每个 Claude Code 终端使用自己的 token:
ANTHROPIC_BASE_URL=http://127.0.0.1:3456 ANTHROPIC_API_KEY=router-token-project-a clauderouter token 只是本地 gateway 的通行证,不是你的上游模型服务商 API key。真实的上游 API key 仍然保存在各自项目的 config 文件里。
- 支持
POST /v1/messages。 - 支持普通 JSON 响应和 streaming SSE 响应。
- 会把请求里的
model替换成当前 route 在本地配置里的真实模型名。 - 默认会往 system prompt 里追加 route 选择说明。
- 只接受 assistant 指令里的已配置 route 名。推荐使用
route字段,model字段只作为向后兼容别名。 - 如果指令缺失或非法,就保持上一轮 route 不变。
- 默认把 route 状态写入
.router-state.json;多客户端模式下每个 client 有自己独立的状态文件。 - 会保留上游路径,例如
https://api.example.com/coding会变成https://api.example.com/coding/v1/messages。
- 不要提交
router.config.json或任何真实 API key。 - 不要把这个 router 暴露到公网。
- 除非你非常清楚自己在做什么,否则保持
listen.host为127.0.0.1。 - assistant 只能选择配置里的本地 route 名,不能选择真实模型名、API key 或 base URL。
- 任何已经提交、公开日志记录、或贴到公开 issue 里的 key 都应该轮换。
运行语法检查:
npm run check运行 fake upstream 集成测试:
npm test测试会启动两个本地 fake upstream,验证 route 切换、模型名重写、非法 route fallback、streaming 指令解析、以及上游路径拼接。
MIT