用 Rust 编写的 Anthropic Claude API 兼容代理,把 Anthropic 格式的请求转换成 Kiro API 请求。
在 hank9999/kiro.rs 的一个 fork 基础上二次开发,主要补充了首字超时重试——解决上游返回 HTTP 200 后静默卡死、现有重试机制完全失效的问题。
Claude Code / Cursor / Cline
│ Anthropic 协议
▼ :8990
kiro-rs-plus
│
▼
Kiro API (AWS CodeWhisperer)
| 功能 | 说明 |
|---|---|
| 首字超时重试 | 上游返回 200 后长时间不推送内容时,在流内部透明重试。客户端只看到稍慢的正常响应,不是错误 |
firstTokenTimeoutSecs |
首个内容事件等待超时,默认 30 秒,0 关闭 |
firstTokenMaxRetries |
首字超时后的最大重试次数,默认 2 |
| Opus 5 / Sonnet 5 支持 | 上游 fork 的版本判断靠「包含 4-x」,claude-opus-5 一条都不匹配会被静默降级。改为按版本段精确匹配并补齐映射、模型列表与 thinking 门控 |
| 透传兜底 | 请求的版本比兜底目标新时(如未来的 Opus 4.9 / Opus 6),按 Kiro 命名合成 wire ID 直接交给上游裁决,而不是静默降级到旧模型。Kiro 上新版本通常不用改代码 |
| 项 | 说明 |
|---|---|
| 包名 | kiro-rs → kiro-rs-plus(二进制名仍为 kiro-rs,不破坏 Dockerfile 与 release 流程) |
docker-compose.yml |
默认改为从源码构建。上游预构建镜像不含本仓库改动,直拉会缺功能 |
| 可测性 | 新增内部 UpstreamReopener trait 作为注入接缝,使重试路径可被测试驱动 |
| 测试 | 新增 7 个测试,用 mock 上游驱动状态机 |
上游偶发返回 HTTP 200、建立 SSE 连接,然后一个字节都不推送——没有错误码,没有断连。
此时所有基于状态码的重试机制全部失效:401/403 换凭据、429/5xx 退避,都需要一个状态码才能触发。请求会一直挂着,客户端只能看到 ping 心跳,无法区分「模型在思考」和「上游已经死了」。
重试在流内部完成,对客户端透明:
| 阶段 | 行为 |
|---|---|
| 请求发出后 | HTTP 200 与响应头立即返回,客户端马上看到连接建立 |
| 等待首字期间 | 只发 ping 保活(25 秒间隔,与原行为一致),不发 message_start |
| 超时触发 | 流内部重新发起上游请求并换掉数据源,此时尚未向客户端写出任何字节 |
| 收到首字 | 补发 message_start,计时器彻底失效 |
| 重试用尽 | 收尾为 stop_reason: error,仍是结构完整的 message |
为什么必须延后 message_start——这是协议约束,不是设计偏好。一旦它发出,该 message 就已开启,重试会让客户端收到两个 message。所以「能否重试」的唯一判据是首个字节是否已写给客户端,延后发送正是为了保住这个窗口。
元数据事件不算首字。 上游首帧通常先来 initialResponseEvent / contextUsageEvent / meteringEvent,这些只是元数据,不代表模型开始生成。若以「收到任意事件」为信号,一个只发元数据然后卡死的上游会被漏过。本实现以 process_kiro_event 返回非空事件为判据(元数据事件返回空 Vec),无需手工枚举事件类型。
重试要重建流上下文。 Prompt cache usage 按凭据统计,而重试经由 call_api_stream → 凭据选择,可能落到另一条凭据上。沿用旧 StreamContext 会把上一条凭据的 cache 数据错记过来。
- 首字之前的读取失败也走重试——客户端尚未收到任何内容,重试严格优于报错。该路径与超时共享同一份重试配额
- 收到首字后,后续 token 之间的正常停顿(模型思考、工具调用间隙)不受任何约束
- 重试走
call_api_stream,因此天然经过凭据选择与 cooldown 降权,卡死的凭据更不容易被再次选中 - 默认 30 秒而非更激进的值:thinking 模型首字前思考十几秒属正常,取值过小会误杀正常请求
用 mock 上游驱动状态机:stream::pending() 精确模拟「200 后永不推送」,并手写合法的 AWS Event Stream 帧(含 prelude / message CRC),走真实解码路径。
| 测试 | 验证 |
|---|---|
first_token_timeout_retries_and_recovers |
静默 → 重试 → 拿到内容,message_start 只出现一次 |
metadata_only_does_not_count_as_first_token |
只发元数据帧后卡死,仍触发重试 |
first_token_retries_exhausted_emits_error_stop |
配额用尽后收尾为 error,且补发 message_start(避免无头 message) |
no_retry_after_first_token_committed |
收到首字后长时间停顿,重开次数为 0 |
read_error_before_first_token_retries |
首字前断流同样重试 |
reopen_failure_terminates_stream |
凭据用尽时立即收尾,不无限重试 |
timeout_zero_disables_retry |
timeout=0 完全关闭 |
- 重试时旧连接依赖
drop释放。在「已发响应头、连接保持、不推数据」场景下,drop是否立即关闭 TCP 取决于 reqwest 连接池行为,未经验证。最坏情况是每次重试泄漏一个挂死连接,上限为firstTokenMaxRetries,不会累积 - 该功能未对真实 Kiro 上游验证。测试覆盖的是状态机逻辑,而非真实网络下的有效性。日志出现
首个内容事件等待超时的 warn 即为实际触发
cargo test 有 4 个失败,全部位于 src/kiro/provider.rs 的 profileArn 注入测试。原因是实现与测试预期不一致:inject_profile_arn(src/kiro/endpoint/ide.rs)在凭据无 profileArn 时会移除请求体中该字段,而测试断言原样返回。属于行为变更后测试未同步,与本仓库改动无关。
以下来自上游 fork,非本仓库改动:
| 修复 | 说明 |
|---|---|
| retry 不再撞同一个凭据 | 原版 affinity 短路会让失败凭据被反复选回,retry 链路加 exclude_ids 强制跳过 |
| 新凭据「雷暴防护」 | 新凭据 recent_usage=0 会被 LB 判为「最少使用」→ 1 秒内被打 47 次 429。改用现有凭据 recent_usage 中位数作 baseline |
| 周期 balance 刷新 | 原版 cache 只是启动时快照,改为 10 分钟周期刷新 + 余额不足主动禁用 |
| cache_tracker TTL 对齐上游 | 原版命中时刷新 expires_at,但 Anthropic 真实 TTL 从首次写入算 |
credentialRpm: 0 真禁用限流 |
原版 0 会落回默认 1-2 秒间隔 + 每日 500 上限,与字面意思相反 |
| 凭据级 IdP / 代理 / Portal 设置 | Admin UI 支持逐条配置 Region、IdC 凭据、HTTP/SOCKS5 代理与 direct 直连 |
| Overages 在线启停 | SSE 实时开关,状态从 Web Portal 与 GetUserUsageAndLimits 同步并持久化 |
| Overage-aware 余额 | 余额与自动禁用使用「基础额度 + 超额额度」,上游未返回 overageEnabled 时不误判 |
| Thinking 兼容增强 | opus-4-7/4-8-thinking 走 adaptive thinking;模型名不带 -thinking 时只要请求带 thinking 参数也启用 |
- Anthropic API 兼容 — 完整支持 Anthropic Claude API 格式
- 流式响应 — SSE 流式输出
- 首字超时重试 — 上游静默卡死自愈
- 多凭据池 — 按优先级自动故障转移,支持 social / idc / external_idp / API Key
- Token 自动刷新 — 含后台周期刷新,避免请求时才刷造成延迟
- 凭据回写 — 多凭据格式下自动回写刷新后的 Token
- 请求体压缩 — 规避上游约 5MiB 请求体上限
- 凭据冷却 — 分类冷却与自动恢复
- 凭据亲和 — 连续对话尽量固定同一凭据
- Thinking 模式 — 支持 extended thinking,按模型版本自动选 adaptive / enabled
- 透传兜底 — Kiro 上新模型版本时无需改代码,也不会静默降级
- 工具调用 — 完整支持 function calling / tool use,含截断检测与软恢复
- 图片处理 — token 计算与自动缩放
- WebSearch — 内置转换逻辑
- Admin 管理 — Web 面板与 API,含余额查询、Overages 启停、凭据级配置
不支持 OpenAI 格式(
/v1/chat/completions)。仅 Anthropic 协议,Claude Code / Cline 可直接用;需要 OpenAI 接口的客户端(如 Codex)用不了。
git clone https://github.com/zenitlab/kiro-rs-plus.git
cd kiro-rs-plus
mkdir -p config
# 准备 config/config.json 与 config/credentials.json(见下方配置)
docker compose up -d
docker compose logs -f容器内
host必须是0.0.0.0,配127.0.0.1只绑容器内环回,端口映射不通。
需要 Rust 1.93+(edition 2024)与 Node.js + pnpm。
# 先构建前端,否则 rust-embed 会因缺少 admin-ui/dist 而编译失败
cd admin-ui && pnpm install --frozen-lockfile && pnpm build && cd ..
cargo build --release
./target/release/kiro-rs -c config/config.json --credentials config/credentials.jsonconfig/config.json:
{
"host": "0.0.0.0",
"port": 8990,
"apiKey": "sk-your-own-key",
"region": "us-east-1"
}config/credentials.json:
{
"refreshToken": "你的 refreshToken",
"authMethod": "social"
}export ANTHROPIC_BASE_URL=http://127.0.0.1:8990
export ANTHROPIC_AUTH_TOKEN=sk-your-own-keyURL 填到根即可,Claude Code 会自己拼 /v1/messages。
| 字段 | 类型 | 默认值 | 描述 |
|---|---|---|---|
host |
string | 127.0.0.1 |
服务监听地址(容器内用 0.0.0.0) |
port |
number | 8080 |
服务监听端口 |
apiKey |
string | - | 客户端认证用的 API Key(必配) |
region |
string | us-east-1 |
AWS 区域 |
authRegion |
string | - | Token 刷新用,未配置回退到 region |
apiRegion |
string | - | API 请求用,未配置回退到 region |
firstTokenTimeoutSecs |
number | 30 |
首字等待超时(秒),0 关闭 |
firstTokenMaxRetries |
number | 2 |
首字超时最大重试次数 |
adminApiKey |
string | - | 配置后启用 Admin API 与 Web 面板 |
credentialRpm |
number | - | 单凭据目标 RPM,0 或未配置用内置策略 |
tlsBackend |
string | rustls |
rustls 或 native-tls |
proxyUrl |
string | - | HTTP/SOCKS5 代理地址 |
proxyUsername / proxyPassword |
string | - | 代理认证 |
machineId |
string | - | 自定义机器码(64 位十六进制),不填自动生成 |
kiroVersion |
string | 0.10.0 |
Kiro 版本号 |
systemVersion |
string | 随机 | 系统版本标识 |
nodeVersion |
string | 22.21.1 |
Node.js 版本标识 |
countTokensApiUrl |
string | - | 外部 count_tokens API 地址 |
countTokensApiKey |
string | - | 外部 count_tokens API 密钥 |
countTokensAuthType |
string | x-api-key |
x-api-key 或 bearer |
promptCacheTtlSeconds |
number | 300 |
本地 Prompt Cache TTL(秒) |
promptCacheAccountingEnabled |
boolean | true |
关闭后不再输出或扣减 cache token |
| 字段 | 类型 | 描述 |
|---|---|---|
refreshToken |
string | OAuth 刷新令牌 |
authMethod |
string | social / idc / external_idp / api_key |
kiroApiKey |
string | Kiro API Key(ksk_ 开头),设置后直接作为 Bearer Token,无需刷新 |
accessToken |
string | 可选,可自动刷新 |
profileArn |
string | 可选,登录时返回 |
expiresAt |
string | Token 过期时间(RFC3339) |
clientId / clientSecret |
string | IdC 认证必填 |
priority |
number | 数字越小越优先,默认 0 |
region / authRegion / apiRegion |
string | 凭据级 Region |
machineId |
string | 凭据级机器码,不填自动派生 |
email |
string | 可选,从 API 获取 |
proxyUrl |
string | 凭据级代理,direct 表示显式直连 |
proxyUsername / proxyPassword |
string | 凭据级代理认证 |
disabled |
boolean | 是否禁用该凭据 |
overageEnabled |
boolean | 是否开启 Overages,自动同步 |
overageCap |
number | Overages 超额上限 |
IdC / Builder-ID / IAM 属于同一种登录方式,统一用 authMethod: "idc"(builder-id / iam 仍可识别,按 idc 处理)。
[
{
"refreshToken": "第一个凭据",
"authMethod": "social",
"priority": 0
},
{
"refreshToken": "第二个凭据",
"authMethod": "idc",
"clientId": "xxx",
"clientSecret": "xxx",
"region": "us-east-2",
"priority": 1,
"proxyUrl": "socks5://proxy.example.com:1080"
},
{
"kiroApiKey": "ksk_xxxxxxxx",
"authMethod": "api_key",
"priority": 2
}
]- 按
priority排序,数字越小越优先 - 自动故障转移到下一个可用凭据
- Token 刷新后自动回写源文件
- Admin UI 的修改也会回写
同一个
refreshToken不能同时用在两处。 Kiro 每次刷新都会作废旧 token,谁先刷谁把对方打死,另一边会 401。kiroApiKey没这个问题,因为它不刷新。
| 变量 | 说明 |
|---|---|
KIRO_API_KEY |
设置后自动追加一条最高优先级的 API Key 凭据 |
CONFIG_PATH |
配置文件路径 |
| 端点 | 方法 | 描述 |
|---|---|---|
/v1/models |
GET | 可用模型列表 |
/v1/messages |
POST | 创建消息 |
/v1/messages/count_tokens |
POST | 估算 Token 数量 |
GET /v1/models 返回固定的能力列表,不按单个凭据区分。
请求的模型名先剥离 -thinking / -agentic 后缀,再按子串匹配(map_model,src/anthropic/converter.rs):
| 请求模型名包含 | 映射到 Kiro 模型 |
|---|---|
opus + 版本段 5 |
claude-opus-5 |
opus + 4-5/4.5 |
claude-opus-4.5 |
opus + 4-7/4.7 |
claude-opus-4.7 |
opus + 4-8/4.8 |
claude-opus-4.8 |
opus + 版本 > 4.6 |
合成后透传(见下) |
opus(其他) |
claude-opus-4.6 |
sonnet + 版本段 5 |
claude-sonnet-5 |
sonnet + 4-6/4.6 |
claude-sonnet-4.6 |
sonnet + 版本 > 4.5 |
合成后透传 |
sonnet(其他) |
claude-sonnet-4.5 |
haiku + 版本 > 4.5 |
合成后透传 |
haiku(其他) |
claude-haiku-4.5 |
模型名不含 sonnet / opus / haiku 时返回 UnsupportedModel 错误,不做兜底。
「版本段 5」不是 contains("5")。 5 代上游 ID 没有小版本号(claude-opus-5),而 4.5 代写作 claude-opus-4-5——两者都含字符 5。所以判据是取系列名之后的第一段版本号,要求它恰好等于 5,这样 claude-opus-4-5-20251101 与 claude-3-5-sonnet 都不会被误判成 5 代。这条边界有专门的回归测试 test_map_model_version_5_does_not_swallow_4_5。
参照 kiro-gateway 的设计原则——网关不做守门员,Kiro API 才是最终裁判。显式分支没命中时不直接降级,先判断版本:
| 请求 | 结果 | 理由 |
|---|---|---|
claude-opus-6 |
透传 claude-opus-6 |
比兜底目标 4.6 新 |
claude-opus-4-9 |
透传 claude-opus-4.9 |
同上,合成时按 Kiro 习惯用 . 分隔 |
claude-haiku-5 |
透传 claude-haiku-5 |
比兜底目标 4.5 新 |
claude-opus-4-1 |
降级 claude-opus-4.6 |
比兜底目标旧,Kiro 无此型号 |
claude-opus-4-20250514 |
降级 claude-opus-4.6 |
8 位数字是日期戳不是版本号,解析为 (4, 无) |
claude-3-5-sonnet-20241022 |
降级 claude-sonnet-4.5 |
系列名之前的数字不算版本号 |
判据是「比兜底目标新」而非「比已知最新版本新」,因为要回答的问题是「降级会不会把用户请求的模型变旧」。若拿 5 当门槛,claude-opus-4-9 按 (4,9) < (5,0) 会被判为无需透传,然后落进 else 静默降级成 4.6——正是要避免的情况。
透传合成的是 Kiro 格式的 ID,不是原样转发客户端传来的名字:客户端可能用 Anthropic 的日期戳命名(claude-opus-6-20270101),上游不认这种写法。
上游若不认合成出来的 ID,表现为请求报错,而不是悄悄换成旧模型——这正是设计意图,错得明显比错得安静好。
Kiro 官方当前提供的 Claude 模型(kiro.dev/docs/models,倍率以 Auto = 1.0x 为基准):
| Kiro 模型 | 上下文 | 倍率 | 本代理 |
|---|---|---|---|
| Claude Opus 5 | 1M | 2.2x | 支持 |
| Claude Opus 4.8 | 1M | 2.2x | 支持 |
| Claude Opus 4.7 | 1M | 2.2x | 支持 |
| Claude Opus 4.6 | 1M | 2.2x | 支持 |
| Claude Opus 4.5 | 200K | 2.2x | 支持 |
| Claude Sonnet 5 | 1M | 1.3x | 支持 |
| Claude Sonnet 4.6 | 1M | 1.3x | 支持 |
| Claude Sonnet 4.5 | 200K | 1.3x | 支持 |
| Claude Sonnet 4.0 | 200K | 1.3x | 归并到 claude-sonnet-4.5 |
| Claude Haiku 4.5 | 200K | 0.4x | 支持 |
Opus 4.7 及更新版本(4.8 / 5,以及未来透传出来的型号)只接受 adaptive thinking,不能用 enabled + budget_tokens 手动预算,映射后自动切到 adaptive + high effort。这条门控按映射结果的版本号判断,所以透传出来的 Opus 6 会自动落到正确路径,不用再改一次代码。Sonnet 全系保留 enabled + budget 兼容路径。
Kiro 后续新增版本时:日期戳变体(claude-opus-5-20270101)本来就归一化,新小版本与新大版本走透传兜底,两种情况都不用改代码。只有想让某个新版本走专门的处理(比如新的 thinking 形态、不同的上下文上限)才需要加显式分支。
Opus 5 / Sonnet 5 的上游 modelId 字面量未在真实账号上验证过。
claude-opus-5/claude-sonnet-5取自 Kiro 官方 changelog 与第三方 Kiro 客户端实测的 wire ID,但本仓库的 20 个测试凭据均指向同一个被封的 AWS 账号,无法发真请求确认。若上游拒绝该 ID,表现为请求返回错误而非降级。
Kiro 同时提供 GPT-5.6 Sol / Terra / Luna、GLM-5、DeepSeek 等非 Claude 模型,本代理不路由。Claude 全系 US / EU 双区可用,实际区域跟随凭据的 profile region。
配置 adminApiKey 后启用。Web 面板在 /admin(不是根路径)。
主要能力:凭据列表与详情、逐条编辑 Region / IdC / 代理、余额查询、Overages SSE 实时启停、失败计数重置。
主要接口:
| 端点 | 方法 | 描述 |
|---|---|---|
/api/admin/credentials |
GET / POST | 获取全部凭据 / 添加凭据 |
/api/admin/credentials/:id |
DELETE | 删除凭据 |
/api/admin/credentials/:id/disabled |
POST | 启用 / 禁用 |
/api/admin/credentials/:id/priority |
POST | 设置优先级 |
/api/admin/credentials/:id/reset |
POST | 重置失败计数 |
/api/admin/credentials/:id/refresh |
POST | 强制刷新 Token |
/api/admin/credentials/:id/balance |
GET | 实时余额并更新缓存 |
/api/admin/credentials/balances/cached |
GET | 全部缓存余额 |
/api/admin/credentials/:id/overage/enable |
POST | 开启 Overages(SSE) |
/api/admin/credentials/:id/portal-settings |
POST | 更新凭据级配置 |
认证同 API:x-api-key 或 Authorization: Bearer。
Admin 权限可启停凭据、改优先级、查余额。若绑定
0.0.0.0且映射端口,同局域网设备均可访问——不放心就把端口映射改成127.0.0.1:8990:8990。
- 凭证安全 —
credentials.json已在.gitignore中,不要提交 - TLS 后端 — 遇到无法刷新 token 或直接返回 error request,尝试把
tlsBackend切成native-tls - Write Failed / 会话卡死 — 通常与输出过长被截断有关,可调低输出 token 上限,参考上游 #22 与 #49
本项目仅供研究使用,Use at your own risk,使用本项目所导致的任何后果由使用人承担,与本项目无关。
本项目与 AWS / Kiro / Anthropic / Claude 等官方无关,不代表官方立场。
Rust + Tokio + Axum + reqwest + rustls,前端 React + Vite + Tailwind,通过 rust-embed 打进单个二进制。
MIT,见 LICENSE。原始版权归 hank9999 and contributors。
地基由 hank9999/kiro.rs 原作者打就,向原作者致敬。上游 fork 贡献了负载均衡与缓存修复。
上游实现参考过以下项目,一并致谢: