KiroProxy User commited on
Commit ·
0edbd7b
1
Parent(s): 0e30efb
chore: repo cleanup and maintenance
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +13 -0
- {KiroProxy/.github → .github}/workflows/build.yml +0 -0
- KiroProxy/.gitignore → .gitignore +1 -0
- .orchids/orchids.json +0 -9
- CAPTURE_GUIDE.md +37 -0
- Dockerfile +2 -2
- KiroProxy/CAPTURE_GUIDE.md +0 -0
- KiroProxy/README.md +0 -425
- KiroProxy/kiro_proxy/main_legacy.py +0 -694
- KiroProxy/legacy/kiro_proxy.py +0 -313
- KiroProxy/requirements.txt +0 -13
- KiroProxy/run.py +0 -14
- KiroProxy/scripts/capture_kiro.py +0 -139
- KiroProxy/scripts/proxy_server.py +0 -135
- README.md +364 -59
- README_HF.md +0 -9
- {KiroProxy/assets → assets}/icon.iconset/icon_128x128.png +0 -0
- {KiroProxy/assets → assets}/icon.iconset/icon_16x16.png +0 -0
- {KiroProxy/assets → assets}/icon.iconset/icon_256x256.png +0 -0
- {KiroProxy/assets → assets}/icon.iconset/icon_32x32.png +0 -0
- {KiroProxy/assets → assets}/icon.iconset/icon_512x512.png +0 -0
- {KiroProxy/assets → assets}/icon.iconset/icon_64x64.png +0 -0
- {KiroProxy/assets → assets}/icon.png +0 -0
- {KiroProxy/assets → assets}/icon.svg +0 -0
- KiroProxy/build.py → build.py +0 -0
- {KiroProxy/examples → examples}/quota_display_example.py +0 -0
- {KiroProxy/examples → examples}/test_quota_display.html +0 -0
- KiroProxy/kiro.svg → kiro.svg +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/__init__.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/__main__.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/auth/__init__.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/auth/device_flow.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/cli.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/config.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/converters/__init__.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/__init__.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/account.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/account_selector.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/admin_auth.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/auth_middleware.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/browser.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/database.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/error_handler.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/flow_monitor.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/history_manager.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/kiro_api.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/persistence.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/protocol_handler.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/quota_cache.py +0 -0
- {KiroProxy/kiro_proxy → kiro_proxy}/core/quota_scheduler.py +0 -0
.dockerignore
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.gitignore
|
| 3 |
+
__pycache__/
|
| 4 |
+
*.pyc
|
| 5 |
+
.pytest_cache/
|
| 6 |
+
.hypothesis/
|
| 7 |
+
.env
|
| 8 |
+
dist/
|
| 9 |
+
build/
|
| 10 |
+
release/
|
| 11 |
+
.venv/
|
| 12 |
+
venv/
|
| 13 |
+
kiro_requests/
|
{KiroProxy/.github → .github}/workflows/build.yml
RENAMED
|
File without changes
|
KiroProxy/.gitignore → .gitignore
RENAMED
|
@@ -40,6 +40,7 @@ flows
|
|
| 40 |
flows_*
|
| 41 |
traffic.mitm
|
| 42 |
*.mitm
|
|
|
|
| 43 |
analyze_har.py
|
| 44 |
parse_*.py
|
| 45 |
*_analysis.txt
|
|
|
|
| 40 |
flows_*
|
| 41 |
traffic.mitm
|
| 42 |
*.mitm
|
| 43 |
+
kiro_requests/
|
| 44 |
analyze_har.py
|
| 45 |
parse_*.py
|
| 46 |
*_analysis.txt
|
.orchids/orchids.json
DELETED
|
@@ -1,9 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"projectId": "c1b5887f-dbf9-4c85-bec3-696c4662d56c",
|
| 3 |
-
"createdAt": 1768697452308,
|
| 4 |
-
"version": "1.0",
|
| 5 |
-
"startupCommands": [
|
| 6 |
-
"bun install; bun dev"
|
| 7 |
-
],
|
| 8 |
-
"templateId": "nextjs"
|
| 9 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CAPTURE_GUIDE.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Kiro 请求抓包指南
|
| 2 |
+
|
| 3 |
+
本项目提供 `scripts/capture_kiro.py`(mitmproxy addon)用于抓取 Kiro IDE 与 AWS 端点的请求/响应,便于调试协议与字段。
|
| 4 |
+
|
| 5 |
+
## 安装依赖
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
pip install mitmproxy
|
| 9 |
+
```
|
| 10 |
+
|
| 11 |
+
## 运行 mitmproxy
|
| 12 |
+
|
| 13 |
+
带 UI:
|
| 14 |
+
|
| 15 |
+
```bash
|
| 16 |
+
mitmproxy --mode regular@8888 -s scripts/capture_kiro.py
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
无 UI:
|
| 20 |
+
|
| 21 |
+
```bash
|
| 22 |
+
mitmdump --mode regular@8888 -s scripts/capture_kiro.py
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
## 配置代理与证书
|
| 26 |
+
|
| 27 |
+
- 将系统或 Kiro IDE 的 HTTP/HTTPS 代理设置为 `127.0.0.1:8888`
|
| 28 |
+
- 如需解密 HTTPS,安装 mitmproxy CA 证书:访问 `http://mitm.it`
|
| 29 |
+
|
| 30 |
+
## 输出文件
|
| 31 |
+
|
| 32 |
+
抓到的请求/响应会输出到 `kiro_requests/`:
|
| 33 |
+
|
| 34 |
+
- `*_request.json`
|
| 35 |
+
- `*_response.json`
|
| 36 |
+
|
| 37 |
+
`kiro_requests/` 已在 `.gitignore` 中忽略。
|
Dockerfile
CHANGED
|
@@ -23,7 +23,7 @@ COPY requirements.txt .
|
|
| 23 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 24 |
|
| 25 |
# 复制项目文件
|
| 26 |
-
COPY
|
| 27 |
|
| 28 |
# 创建数据目录
|
| 29 |
RUN mkdir -p /app/data
|
|
@@ -36,4 +36,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
| 36 |
CMD curl -f http://localhost:7860/api/status || exit 1
|
| 37 |
|
| 38 |
# 启动命令
|
| 39 |
-
CMD ["python", "
|
|
|
|
| 23 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 24 |
|
| 25 |
# 复制项目文件
|
| 26 |
+
COPY . .
|
| 27 |
|
| 28 |
# 创建数据目录
|
| 29 |
RUN mkdir -p /app/data
|
|
|
|
| 36 |
CMD curl -f http://localhost:7860/api/status || exit 1
|
| 37 |
|
| 38 |
# 启动命令
|
| 39 |
+
CMD ["python", "run.py", "7860"]
|
KiroProxy/CAPTURE_GUIDE.md
DELETED
|
File without changes
|
KiroProxy/README.md
DELETED
|
@@ -1,425 +0,0 @@
|
|
| 1 |
-
<p align="center">
|
| 2 |
-
<img src="assets/icon.svg" width="80" height="96" alt="Kiro Proxy">
|
| 3 |
-
</p>
|
| 4 |
-
|
| 5 |
-
<h1 align="center">Kiro API Proxy</h1>
|
| 6 |
-
|
| 7 |
-
<p align="center">
|
| 8 |
-
Kiro IDE API 反向代理服务器,支持多账号轮询、Token 自动刷新、配额管理
|
| 9 |
-
</p>
|
| 10 |
-
|
| 11 |
-
<p align="center">
|
| 12 |
-
<a href="#功能特性">功能</a> •
|
| 13 |
-
<a href="#快速开始">快速开始</a> •
|
| 14 |
-
<a href="#cli-配置">CLI 配置</a> •
|
| 15 |
-
<a href="#api-端点">API</a> •
|
| 16 |
-
<a href="#许可证">许可证</a>
|
| 17 |
-
</p>
|
| 18 |
-
|
| 19 |
-
---
|
| 20 |
-
|
| 21 |
-
> **⚠️ 测试说明**
|
| 22 |
-
>
|
| 23 |
-
> 本项目支持 **Claude Code**、**Codex CLI**、**Gemini CLI** 三种客户端,工具调用功能已全面支持。
|
| 24 |
-
|
| 25 |
-
## 功能特性
|
| 26 |
-
|
| 27 |
-
### 核心功能
|
| 28 |
-
- **多协议支持** - OpenAI / Anthropic / Gemini 三种协议兼容
|
| 29 |
-
- **完整工具调用** - 三种协议的工具调用功能全面支持
|
| 30 |
-
- **图片理解** - 支持 Claude Code / Codex CLI 图片输入
|
| 31 |
-
- **网络搜索** - 支持 Claude Code / Codex CLI 网络搜索工具
|
| 32 |
-
- **思考功能** - 支持 Claude 的扩展思考功能(Extended Thinking)
|
| 33 |
-
- **多账号轮询(默认随机)** - 每次请求随机切换账号,分散压力,避免单账号 RPM 过高
|
| 34 |
-
- **会话粘性(可选)** - 非 `random` 策略下,同一会话 60 秒内使用同一账号,保持上下文
|
| 35 |
-
- **Web UI** - 简洁的管理界面,支持监控、日志、设置
|
| 36 |
-
|
| 37 |
-
### v1.7.1 新功能
|
| 38 |
-
- **Windows 支持补强** - 注册表浏览器检测 + PATH 回退,兼容便携版
|
| 39 |
-
- **打包资源修复** - PyInstaller 打包后可正常加载图标与内置文档
|
| 40 |
-
- **Token 扫描稳定性** - Windows 路径编码处理修复
|
| 41 |
-
|
| 42 |
-
### v1.6.3 新功能
|
| 43 |
-
- **命令行工具 (CLI)** - 无 GUI 服务器也能轻松管理
|
| 44 |
-
- `python run.py accounts list` - 列出账号
|
| 45 |
-
- `python run.py accounts export/import` - 导出/导入账号
|
| 46 |
-
- `python run.py accounts add` - 交互式添加 Token
|
| 47 |
-
- `python run.py accounts scan` - 扫描本地 Token
|
| 48 |
-
- `python run.py login google/github` - 命令行登录
|
| 49 |
-
- `python run.py login remote` - 生成远程登录链接
|
| 50 |
-
- **远程登录链接** - 在有浏览器的机器上完成授权,Token 自动同步
|
| 51 |
-
- **账号导入导出** - 跨机器迁移账号配置
|
| 52 |
-
- **手动添加 Token** - 直接粘贴 accessToken/refreshToken
|
| 53 |
-
|
| 54 |
-
### v1.6.2 新功能
|
| 55 |
-
- **Codex CLI 完整支持** - 使用 OpenAI Responses API (`/v1/responses`)
|
| 56 |
-
- 完整工具调用支持(shell、file 等所有工具)
|
| 57 |
-
- 图片输入支持(`input_image` 类型)
|
| 58 |
-
- 网络搜索支持(`web_search` 工具)
|
| 59 |
-
- 错误代码映射(rate_limit、context_length 等)
|
| 60 |
-
- **Claude Code 增强** - 图片理解和网络搜索完整支持
|
| 61 |
-
- 支持 Anthropic 和 OpenAI 两种图片格式
|
| 62 |
-
- 支持 `web_search` / `web_search_20250305` 工具
|
| 63 |
-
|
| 64 |
-
### v1.6.1 新功能
|
| 65 |
-
- **请求限速** - 通过限制请求频率降低账号封禁风险
|
| 66 |
-
- 每账号最小请求间隔
|
| 67 |
-
- 每账号每分钟最大请求数
|
| 68 |
-
- 全局每分钟最大请求数
|
| 69 |
-
- WebUI 设置页面可配置
|
| 70 |
-
- **账号封禁检测** - 自动检测 TEMPORARILY_SUSPENDED 错误
|
| 71 |
-
- 友好的错误日志输出
|
| 72 |
-
- 自动禁用被封禁账号
|
| 73 |
-
- 自动切换到其他可用账号
|
| 74 |
-
- **统一错误处理** - 三种协议使用统一的错误分类和处理
|
| 75 |
-
|
| 76 |
-
### v1.6.0 功能
|
| 77 |
-
- **历史消息管理** - 4 种策略处理对话长度限制,可自由组合
|
| 78 |
-
- 自动截断:发送前优先保留最新上下文并摘要前文,必要时按数量/字符数截断
|
| 79 |
-
- 智能摘要:用 AI 生成早期对话摘要,保留关键信息
|
| 80 |
-
- 摘要缓存:历史变化不大时复用最近摘要,减少重复 LLM 调用(默认启用)
|
| 81 |
-
- 错误重试:遇到长度错误时自动截断重试(默认启用)
|
| 82 |
-
- 预估检测:预估 token 数量,超限预先截断
|
| 83 |
-
- **Gemini 工具调用** - 完整支持 functionDeclarations/functionCall/functionResponse
|
| 84 |
-
- **设置页面** - WebUI 新增设置标签页,可配置历史消息管理策略
|
| 85 |
-
|
| 86 |
-
### v1.5.0 功能
|
| 87 |
-
- **用量查询** - 查询账号配额使用情况,显示已用/余额/使用率
|
| 88 |
-
- **多登录方式** - 支持 Google / GitHub / AWS Builder ID 三种登录方式
|
| 89 |
-
- **流量监控** - 完整的 LLM 请求监控,支持搜索、过滤、导出
|
| 90 |
-
- **浏览器选择** - 自动检测已安装浏览器,支持无痕模式
|
| 91 |
-
- **文档中心** - 内置帮助文档,左侧目录 + 右侧 Markdown 渲染
|
| 92 |
-
|
| 93 |
-
### v1.4.0 功能
|
| 94 |
-
- **Token 预刷新** - 后台每 5 分钟检查,提前 15 分钟自动刷新
|
| 95 |
-
- **健康检查** - 每 10 分钟检测账号可用性,自动标记状态
|
| 96 |
-
- **请求统计增强** - 按账号/模型统计,24 小时趋势
|
| 97 |
-
- **请求重试机制** - 网络错误/5xx 自动重试,指数退避
|
| 98 |
-
|
| 99 |
-
## 工具调用支持
|
| 100 |
-
|
| 101 |
-
| 功能 | Anthropic (Claude Code) | OpenAI (Codex CLI) | Gemini |
|
| 102 |
-
|------|------------------------|-------------------|--------|
|
| 103 |
-
| 工具定义 | ✅ `tools` | ✅ `tools.function` | ✅ `functionDeclarations` |
|
| 104 |
-
| 工具调用响应 | ✅ `tool_use` | ✅ `tool_calls` | ✅ `functionCall` |
|
| 105 |
-
| 工具结果 | ✅ `tool_result` | �� `tool` 角色消息 | ✅ `functionResponse` |
|
| 106 |
-
| 强制工具调用 | ✅ `tool_choice` | ✅ `tool_choice` | ✅ `toolConfig.mode` |
|
| 107 |
-
| 工具数量限制 | ✅ 50 个 | ✅ 50 个 | ✅ 50 个 |
|
| 108 |
-
| 历史消息修复 | ✅ | ✅ | ✅ |
|
| 109 |
-
| 图片理解 | ✅ | ✅ | ❌ |
|
| 110 |
-
| 网络搜索 | ✅ | ✅ | ❌ |
|
| 111 |
-
|
| 112 |
-
## 已知限制
|
| 113 |
-
|
| 114 |
-
### 对话长度限制
|
| 115 |
-
|
| 116 |
-
Kiro API 有输入长度限制。当对话历史过长时,会返回错误:
|
| 117 |
-
|
| 118 |
-
```
|
| 119 |
-
Input is too long. (CONTENT_LENGTH_EXCEEDS_THRESHOLD)
|
| 120 |
-
```
|
| 121 |
-
|
| 122 |
-
#### 自动处理(v1.6.0+)
|
| 123 |
-
|
| 124 |
-
代理内置了历史消息管理功能,可在「设置」页面配置:
|
| 125 |
-
|
| 126 |
-
- **错误重试**(默认):遇到长度错误时自动截断并重试
|
| 127 |
-
- **智能摘要**:用 AI 生成早期对话摘要,保留关键信息
|
| 128 |
-
- **摘要缓存**(默认):历史变化不大时复用最近摘要,减少重复 LLM 调用
|
| 129 |
-
- **自动截断**:每次请求前优先保留最新上下文并摘要前文,必要时按数量/字符数截断
|
| 130 |
-
- **预估检测**:预估 token 数量,超限预先截断
|
| 131 |
-
|
| 132 |
-
如需 **关闭自动压缩/重试**(超限时直接报错),可设置环境变量 `KIROPROXY_HISTORY_ERROR_RETRY=0`,或将历史配置的 `strategies` 中移除 `error_retry`。
|
| 133 |
-
|
| 134 |
-
摘要缓存可通过以下配置项调整(默认值):
|
| 135 |
-
- `summary_cache_enabled`: `true`
|
| 136 |
-
- `summary_cache_min_delta_messages`: `3`
|
| 137 |
-
- `summary_cache_min_delta_chars`: `4000`
|
| 138 |
-
- `summary_cache_max_age_seconds`: `180`
|
| 139 |
-
|
| 140 |
-
#### 手动处理
|
| 141 |
-
|
| 142 |
-
1. 在 Claude Code 中输入 `/clear` 清空对话历史
|
| 143 |
-
2. 告诉 AI 你之前在做什么,它会读取代码文件恢复上下文
|
| 144 |
-
|
| 145 |
-
## 快速开始
|
| 146 |
-
|
| 147 |
-
### 方式一:下载预编译版本
|
| 148 |
-
|
| 149 |
-
从 [Releases](../../releases) 下载对应平台的安装包,解压后直接运行。
|
| 150 |
-
|
| 151 |
-
### 方式二:从源码运行
|
| 152 |
-
|
| 153 |
-
```bash
|
| 154 |
-
# 克隆项目
|
| 155 |
-
git clone https://github.com/yourname/kiro-proxy.git
|
| 156 |
-
cd kiro-proxy
|
| 157 |
-
|
| 158 |
-
# 创建虚拟环境
|
| 159 |
-
python -m venv venv
|
| 160 |
-
source venv/bin/activate # Windows: venv\Scripts\activate
|
| 161 |
-
|
| 162 |
-
# 安装依赖
|
| 163 |
-
pip install -r requirements.txt
|
| 164 |
-
|
| 165 |
-
# 运行
|
| 166 |
-
python run.py
|
| 167 |
-
|
| 168 |
-
# 或指定端口
|
| 169 |
-
python run.py 8081
|
| 170 |
-
```
|
| 171 |
-
|
| 172 |
-
启动后访问 http://localhost:8080
|
| 173 |
-
|
| 174 |
-
### 命令行工具 (CLI)
|
| 175 |
-
|
| 176 |
-
无 GUI 服务器可使用 CLI 管理账号:
|
| 177 |
-
|
| 178 |
-
```bash
|
| 179 |
-
# 账号管理
|
| 180 |
-
python run.py accounts list # 列出账号
|
| 181 |
-
python run.py accounts export -o acc.json # 导出账号
|
| 182 |
-
python run.py accounts import acc.json # 导入账号
|
| 183 |
-
python run.py accounts add # 交互式添加 Token
|
| 184 |
-
python run.py accounts scan --auto # 扫描并自动添加本地 Token
|
| 185 |
-
|
| 186 |
-
# 登录
|
| 187 |
-
python run.py login google # Google 登录
|
| 188 |
-
python run.py login github # GitHub 登录
|
| 189 |
-
python run.py login remote --host myserver.com:8080 # 生成远程登录链接
|
| 190 |
-
|
| 191 |
-
# 服务
|
| 192 |
-
python run.py serve # 启动服务 (默认 8080)
|
| 193 |
-
python run.py serve -p 8081 # 指定端口
|
| 194 |
-
python run.py status # 查看状态
|
| 195 |
-
```
|
| 196 |
-
|
| 197 |
-
### 登录获取 Token
|
| 198 |
-
|
| 199 |
-
**方式一:在线登录(推荐)**
|
| 200 |
-
1. 打开 Web UI,点击「在线登录」
|
| 201 |
-
2. 选择登录方式:Google / GitHub / AWS Builder ID
|
| 202 |
-
3. 在浏览器中完成授权
|
| 203 |
-
4. 账号自动添加
|
| 204 |
-
|
| 205 |
-
**方式二:扫描 Token**
|
| 206 |
-
1. 打开 Kiro IDE,使用 Google/GitHub 账号登录
|
| 207 |
-
2. 登录成功后 token 自动保存到 `~/.aws/sso/cache/`
|
| 208 |
-
3. 在 Web UI 点击「扫描 Token」添加账号
|
| 209 |
-
|
| 210 |
-
## CLI 配置
|
| 211 |
-
|
| 212 |
-
### 模型对照表
|
| 213 |
-
|
| 214 |
-
| Kiro 模型 | 能力 | Claude Code | Codex |
|
| 215 |
-
|-----------|------|-------------|-------|
|
| 216 |
-
| `claude-sonnet-4` | ⭐⭐⭐ 推荐 | `claude-sonnet-4` | `gpt-4o` |
|
| 217 |
-
| `claude-sonnet-4.5` | ⭐⭐⭐⭐ 更强 | `claude-sonnet-4.5` | `gpt-4o` |
|
| 218 |
-
| `claude-haiku-4.5` | ⚡ 快速 | `claude-haiku-4.5` | `gpt-4o-mini` |
|
| 219 |
-
|
| 220 |
-
### Claude Code 配置
|
| 221 |
-
|
| 222 |
-
```
|
| 223 |
-
名称: Kiro Proxy
|
| 224 |
-
API Key: any
|
| 225 |
-
Base URL: http://localhost:8080
|
| 226 |
-
模型: claude-sonnet-4
|
| 227 |
-
```
|
| 228 |
-
|
| 229 |
-
### Codex 配置
|
| 230 |
-
|
| 231 |
-
Codex CLI 使用 OpenAI Responses API,配置如下:
|
| 232 |
-
|
| 233 |
-
```bash
|
| 234 |
-
# 设置环境变量
|
| 235 |
-
export OPENAI_API_KEY=any
|
| 236 |
-
export OPENAI_BASE_URL=http://localhost:8080/v1
|
| 237 |
-
|
| 238 |
-
# 运行 Codex
|
| 239 |
-
codex
|
| 240 |
-
```
|
| 241 |
-
|
| 242 |
-
或在 `~/.codex/config.toml` 中配置:
|
| 243 |
-
|
| 244 |
-
```toml
|
| 245 |
-
[providers.openai]
|
| 246 |
-
api_key = "any"
|
| 247 |
-
base_url = "http://localhost:8080/v1"
|
| 248 |
-
```
|
| 249 |
-
|
| 250 |
-
## 思考功能支持
|
| 251 |
-
|
| 252 |
-
### 什么是思考功能
|
| 253 |
-
|
| 254 |
-
思考功能(Extended Thinking)允许 Claude 在生成回答前展示其思考过程,帮助用户理解 AI 的推理步骤。
|
| 255 |
-
|
| 256 |
-
### 如何使用
|
| 257 |
-
|
| 258 |
-
在请求中添加 `thinking`(或对应协议的 thinking 配置)即可启用:
|
| 259 |
-
|
| 260 |
-
```json
|
| 261 |
-
{
|
| 262 |
-
"model": "claude-sonnet-4.5",
|
| 263 |
-
"messages": [
|
| 264 |
-
{
|
| 265 |
-
"role": "user",
|
| 266 |
-
"content": "解释一下量子计算的原理"
|
| 267 |
-
}
|
| 268 |
-
],
|
| 269 |
-
"thinking": {
|
| 270 |
-
"thinking_type": "enabled",
|
| 271 |
-
"budget_tokens": 20000
|
| 272 |
-
},
|
| 273 |
-
"stream": true
|
| 274 |
-
}
|
| 275 |
-
```
|
| 276 |
-
|
| 277 |
-
OpenAI Chat Completions (`POST /v1/chat/completions`) 也支持:
|
| 278 |
-
|
| 279 |
-
```json
|
| 280 |
-
{
|
| 281 |
-
"model": "gpt-4o",
|
| 282 |
-
"messages": [{"role": "user", "content": "解释一下量子计算的原理"}],
|
| 283 |
-
"thinking": { "type": "enabled" },
|
| 284 |
-
"stream": true
|
| 285 |
-
}
|
| 286 |
-
```
|
| 287 |
-
|
| 288 |
-
OpenAI Responses (`POST /v1/responses`) 也支持:
|
| 289 |
-
|
| 290 |
-
```json
|
| 291 |
-
{
|
| 292 |
-
"model": "gpt-4o",
|
| 293 |
-
"input": "解释一下量子计算的原理",
|
| 294 |
-
"thinking": { "type": "enabled" }
|
| 295 |
-
}
|
| 296 |
-
```
|
| 297 |
-
|
| 298 |
-
Gemini generateContent (`POST /v1/models/{model}:generateContent`) 也支持:
|
| 299 |
-
|
| 300 |
-
```json
|
| 301 |
-
{
|
| 302 |
-
"contents": [{"role": "user", "parts": [{"text": "解释一下量子计算的原理"}]}],
|
| 303 |
-
"generationConfig": {
|
| 304 |
-
"thinkingConfig": { "includeThoughts": true }
|
| 305 |
-
}
|
| 306 |
-
}
|
| 307 |
-
```
|
| 308 |
-
|
| 309 |
-
### 参数说明
|
| 310 |
-
|
| 311 |
-
- `thinking_type`: 思考类型,设为 `"enabled"` 启用思考功能
|
| 312 |
-
- `budget_tokens`: 思考过程的 token 预算(不传则视为无限制)
|
| 313 |
-
|
| 314 |
-
### 响应格式
|
| 315 |
-
|
| 316 |
-
启用思考功能后,流式响应会包含两种内容块:
|
| 317 |
-
|
| 318 |
-
1. **思考块**(type: "thinking"):展示 AI 的思考过程
|
| 319 |
-
2. **文本块**(type: "text"):最终的回答内容
|
| 320 |
-
|
| 321 |
-
示例响应:
|
| 322 |
-
```
|
| 323 |
-
data: {"type":"content_block_start","index":1,"content_block":{"type":"thinking","thinking":""}}
|
| 324 |
-
data: {"type":"content_block_delta","index":1,"delta":{"type":"thinking_delta","thinking":"让我思考一下量子计算的原理..."}}
|
| 325 |
-
data: {"type":"content_block_stop","index":1}
|
| 326 |
-
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
|
| 327 |
-
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"量子计算是一种..."}}
|
| 328 |
-
data: {"type":"content_block_stop","index":0}
|
| 329 |
-
```
|
| 330 |
-
|
| 331 |
-
## API 端点
|
| 332 |
-
|
| 333 |
-
| 协议 | 端点 | 用途 |
|
| 334 |
-
|------|------|------|
|
| 335 |
-
| OpenAI | `POST /v1/chat/completions` | Chat Completions API |
|
| 336 |
-
| OpenAI | `POST /v1/responses` | Responses API (Codex CLI) |
|
| 337 |
-
| OpenAI | `GET /v1/models` | 模型列表 |
|
| 338 |
-
| Anthropic | `POST /v1/messages` | Claude Code |
|
| 339 |
-
| Anthropic | `POST /v1/messages/count_tokens` | Token 计数 |
|
| 340 |
-
| Gemini | `POST /v1/models/{model}:generateContent` | Gemini CLI |
|
| 341 |
-
|
| 342 |
-
### 管理 API
|
| 343 |
-
|
| 344 |
-
| 端点 | 方法 | 说明 |
|
| 345 |
-
|------|------|------|
|
| 346 |
-
| `/api/accounts` | GET | 获取所有账号状态 |
|
| 347 |
-
| `/api/accounts/{id}` | GET | 获取账号详情 |
|
| 348 |
-
| `/api/accounts/{id}/usage` | GET | 获取账号用量信息 |
|
| 349 |
-
| `/api/accounts/{id}/refresh` | POST | 刷新账号 Token |
|
| 350 |
-
| `/api/accounts/{id}/restore` | POST | 恢复账号(从冷却状态) |
|
| 351 |
-
| `/api/accounts/refresh-all` | POST | 刷新所有即将过期的 Token |
|
| 352 |
-
| `/api/flows` | GET | 获取流量记录 |
|
| 353 |
-
| `/api/flows/stats` | GET | 获取流量统计 |
|
| 354 |
-
| `/api/flows/{id}` | GET | 获取流量详情 |
|
| 355 |
-
| `/api/quota` | GET | 获取配额状态 |
|
| 356 |
-
| `/api/stats` | GET | 获取统计信息 |
|
| 357 |
-
| `/api/health-check` | POST | 手动触发健康检查 |
|
| 358 |
-
| `/api/browsers` | GET | 获取可用浏览器列表 |
|
| 359 |
-
| `/api/docs` | GET | 获取文档列表 |
|
| 360 |
-
| `/api/docs/{id}` | GET | 获取文档内容 |
|
| 361 |
-
|
| 362 |
-
## 项目结构
|
| 363 |
-
|
| 364 |
-
```
|
| 365 |
-
kiro_proxy/
|
| 366 |
-
├── main.py # FastAPI 应用入口
|
| 367 |
-
├── config.py # 全局配置
|
| 368 |
-
├── converters.py # 协议转换
|
| 369 |
-
│
|
| 370 |
-
├── core/ # 核心模块
|
| 371 |
-
│ ├── account.py # 账号管理
|
| 372 |
-
│ ├── state.py # 全局状态
|
| 373 |
-
│ ├── persistence.py # 配置持久化
|
| 374 |
-
│ ├── scheduler.py # 后台任务调度
|
| 375 |
-
│ ├── stats.py # 请求统计
|
| 376 |
-
│ ├── retry.py # 重试机制
|
| 377 |
-
│ ├── browser.py # 浏览器检测
|
| 378 |
-
│ ├── flow_monitor.py # 流量监控
|
| 379 |
-
│ └── usage.py # 用量查询
|
| 380 |
-
│
|
| 381 |
-
├── credential/ # 凭证管理
|
| 382 |
-
│ ├── types.py # KiroCredentials
|
| 383 |
-
│ ├── fingerprint.py # Machine ID 生成
|
| 384 |
-
│ ├── quota.py # 配额管理器
|
| 385 |
-
│ └── refresher.py # Token 刷新
|
| 386 |
-
│
|
| 387 |
-
├── auth/ # 认证模块
|
| 388 |
-
│ └── device_flow.py # Device Code Flow / Social Auth
|
| 389 |
-
│
|
| 390 |
-
├── handlers/ # API 处理器
|
| 391 |
-
│ ├── anthropic.py # /v1/messages
|
| 392 |
-
│ ├── openai.py # /v1/chat/completions
|
| 393 |
-
│ ├── responses.py # /v1/responses (Codex CLI)
|
| 394 |
-
│ ├── gemini.py # /v1/models/{model}:generateContent
|
| 395 |
-
│ └── admin.py # 管理 API
|
| 396 |
-
│
|
| 397 |
-
├── cli.py # 命令行工具
|
| 398 |
-
│
|
| 399 |
-
├── docs/ # 内置文档
|
| 400 |
-
│ ├── 01-quickstart.md # 快速开始
|
| 401 |
-
│ ├── 02-features.md # 功能特性
|
| 402 |
-
│ ├── 03-faq.md # 常见问题
|
| 403 |
-
│ └── 04-api.md # API 参考
|
| 404 |
-
│
|
| 405 |
-
└── web/
|
| 406 |
-
└── html.py # Web UI (组件化单文件)
|
| 407 |
-
```
|
| 408 |
-
|
| 409 |
-
## 构建
|
| 410 |
-
|
| 411 |
-
```bash
|
| 412 |
-
# 安装构建依赖
|
| 413 |
-
pip install pyinstaller
|
| 414 |
-
|
| 415 |
-
# 构建
|
| 416 |
-
python build.py
|
| 417 |
-
```
|
| 418 |
-
|
| 419 |
-
输出文件在 `dist/` 目录。
|
| 420 |
-
|
| 421 |
-
## 免责声明
|
| 422 |
-
|
| 423 |
-
本项目仅供学习研究,禁止商用。使用本项目产生的任何后果由使用者自行承担,与作者无关。
|
| 424 |
-
|
| 425 |
-
本项目与 Kiro / AWS / Anthropic 官方无关。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
KiroProxy/kiro_proxy/main_legacy.py
DELETED
|
@@ -1,694 +0,0 @@
|
|
| 1 |
-
"""Kiro API Proxy - 主应用"""
|
| 2 |
-
import json
|
| 3 |
-
import uuid
|
| 4 |
-
import httpx
|
| 5 |
-
import sys
|
| 6 |
-
import time
|
| 7 |
-
from pathlib import Path
|
| 8 |
-
from contextlib import asynccontextmanager
|
| 9 |
-
from fastapi import FastAPI, Request, HTTPException
|
| 10 |
-
from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
|
| 11 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 12 |
-
|
| 13 |
-
from . import __version__
|
| 14 |
-
from .config import MODELS_URL
|
| 15 |
-
from .core import state, scheduler, stats_manager, get_quota_scheduler, get_refresh_manager
|
| 16 |
-
from .handlers import anthropic, openai, gemini, admin
|
| 17 |
-
from .handlers import responses as responses_handler
|
| 18 |
-
from .web.html import HTML_PAGE
|
| 19 |
-
from .credential import generate_machine_id, get_kiro_version
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
def get_resource_path(relative_path: str) -> Path:
|
| 23 |
-
"""获取资源文件路径,支持从打包资源读取"""
|
| 24 |
-
base_path = Path(sys._MEIPASS) if hasattr(sys, '_MEIPASS') else Path(__file__).parent.parent
|
| 25 |
-
return base_path / relative_path
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
@asynccontextmanager
|
| 29 |
-
async def lifespan(app: FastAPI):
|
| 30 |
-
"""应用生命周期管理"""
|
| 31 |
-
# 启动时
|
| 32 |
-
await scheduler.start()
|
| 33 |
-
|
| 34 |
-
# 初始化刷新管理器
|
| 35 |
-
refresh_manager = get_refresh_manager()
|
| 36 |
-
refresh_manager.set_accounts_getter(lambda: state.accounts)
|
| 37 |
-
|
| 38 |
-
# 启动时先刷新所有需要刷新的 Token,避免首次请求使用过期 Token
|
| 39 |
-
accounts = state.accounts
|
| 40 |
-
if accounts:
|
| 41 |
-
print(f"[Startup] 检查 {len(accounts)} 个账号的 Token 状态...")
|
| 42 |
-
for account in accounts:
|
| 43 |
-
if account.enabled and refresh_manager.should_refresh_token(account):
|
| 44 |
-
try:
|
| 45 |
-
success, msg = await refresh_manager.refresh_token_if_needed(account)
|
| 46 |
-
if success:
|
| 47 |
-
print(f"[Startup] 账号 {account.name} Token 刷新成功")
|
| 48 |
-
else:
|
| 49 |
-
print(f"[Startup] 账号 {account.name} Token 刷新失败: {msg}")
|
| 50 |
-
except Exception as e:
|
| 51 |
-
print(f"[Startup] 账号 {account.name} Token 刷新异常: {e}")
|
| 52 |
-
|
| 53 |
-
# 启动额度调度器(在 Token 刷新之后,避免首次额度获取使用过期 Token)
|
| 54 |
-
quota_scheduler = get_quota_scheduler()
|
| 55 |
-
quota_scheduler.set_accounts_getter(lambda: state.accounts)
|
| 56 |
-
await quota_scheduler.start()
|
| 57 |
-
|
| 58 |
-
await refresh_manager.start_auto_refresh()
|
| 59 |
-
|
| 60 |
-
yield
|
| 61 |
-
|
| 62 |
-
# 关闭时
|
| 63 |
-
await refresh_manager.stop_auto_refresh()
|
| 64 |
-
await quota_scheduler.stop()
|
| 65 |
-
await scheduler.stop()
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
app = FastAPI(title="Kiro API Proxy", docs_url="/docs", redoc_url=None, lifespan=lifespan)
|
| 69 |
-
|
| 70 |
-
app.add_middleware(
|
| 71 |
-
CORSMiddleware,
|
| 72 |
-
allow_origins=["*"],
|
| 73 |
-
allow_credentials=True,
|
| 74 |
-
allow_methods=["*"],
|
| 75 |
-
allow_headers=["*"],
|
| 76 |
-
)
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
@app.middleware("http")
|
| 80 |
-
async def log_requests(request: Request, call_next):
|
| 81 |
-
start_time = time.time()
|
| 82 |
-
path = request.url.path
|
| 83 |
-
method = request.method
|
| 84 |
-
|
| 85 |
-
body_str = "<skipped>"
|
| 86 |
-
|
| 87 |
-
print(f"[Request] {method} {path} | Body: {body_str}")
|
| 88 |
-
|
| 89 |
-
response = await call_next(request)
|
| 90 |
-
|
| 91 |
-
duration = (time.time() - start_time) * 1000
|
| 92 |
-
print(f"[Response] {method} {path} - {response.status_code} ({duration:.2f}ms)")
|
| 93 |
-
return response
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
# ==================== Web UI ====================
|
| 97 |
-
|
| 98 |
-
@app.get("/", response_class=HTMLResponse)
|
| 99 |
-
async def index():
|
| 100 |
-
return HTML_PAGE
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
@app.get("/assets/{path:path}")
|
| 104 |
-
async def serve_assets(path: str):
|
| 105 |
-
"""提供静态资源"""
|
| 106 |
-
file_path = get_resource_path("assets") / path
|
| 107 |
-
if file_path.exists():
|
| 108 |
-
content_type = "image/svg+xml" if path.endswith(".svg") else "application/octet-stream"
|
| 109 |
-
return StreamingResponse(open(file_path, "rb"), media_type=content_type)
|
| 110 |
-
raise HTTPException(status_code=404)
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
# ==================== API 端点 ====================
|
| 114 |
-
|
| 115 |
-
@app.get("/v1/models")
|
| 116 |
-
async def models():
|
| 117 |
-
"""获取可用模型列表"""
|
| 118 |
-
try:
|
| 119 |
-
account = state.get_available_account()
|
| 120 |
-
if not account:
|
| 121 |
-
raise Exception("No available account")
|
| 122 |
-
|
| 123 |
-
token = account.get_token()
|
| 124 |
-
machine_id = account.get_machine_id()
|
| 125 |
-
kiro_version = get_kiro_version()
|
| 126 |
-
|
| 127 |
-
headers = {
|
| 128 |
-
"content-type": "application/json",
|
| 129 |
-
"x-amz-user-agent": f"aws-sdk-js/1.0.0 KiroIDE-{kiro_version}-{machine_id}",
|
| 130 |
-
"amz-sdk-invocation-id": str(uuid.uuid4()),
|
| 131 |
-
"Authorization": f"Bearer {token}",
|
| 132 |
-
}
|
| 133 |
-
async with httpx.AsyncClient(verify=False, timeout=30) as client:
|
| 134 |
-
resp = await client.get(MODELS_URL, headers=headers, params={"origin": "AI_EDITOR"})
|
| 135 |
-
if resp.status_code == 200:
|
| 136 |
-
data = resp.json()
|
| 137 |
-
return {
|
| 138 |
-
"object": "list",
|
| 139 |
-
"data": [
|
| 140 |
-
{
|
| 141 |
-
"id": m["modelId"],
|
| 142 |
-
"object": "model",
|
| 143 |
-
"owned_by": "kiro",
|
| 144 |
-
"name": m["modelName"],
|
| 145 |
-
}
|
| 146 |
-
for m in data.get("models", [])
|
| 147 |
-
]
|
| 148 |
-
}
|
| 149 |
-
except Exception:
|
| 150 |
-
pass
|
| 151 |
-
|
| 152 |
-
# 降级返回静态列表
|
| 153 |
-
return {"object": "list", "data": [
|
| 154 |
-
{"id": "auto", "object": "model", "owned_by": "kiro", "name": "Auto"},
|
| 155 |
-
{"id": "claude-sonnet-4.5", "object": "model", "owned_by": "kiro", "name": "Claude Sonnet 4.5"},
|
| 156 |
-
{"id": "claude-sonnet-4", "object": "model", "owned_by": "kiro", "name": "Claude Sonnet 4"},
|
| 157 |
-
{"id": "claude-haiku-4.5", "object": "model", "owned_by": "kiro", "name": "Claude Haiku 4.5"},
|
| 158 |
-
]}
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
# Anthropic 协议
|
| 162 |
-
@app.post("/v1/messages")
|
| 163 |
-
async def anthropic_messages(request: Request):
|
| 164 |
-
print(f"[Main] Received /v1/messages request from {request.client.host}")
|
| 165 |
-
return await anthropic.handle_messages(request)
|
| 166 |
-
|
| 167 |
-
@app.post("/v1/messages/count_tokens")
|
| 168 |
-
async def anthropic_count_tokens(request: Request):
|
| 169 |
-
return await anthropic.handle_count_tokens(request)
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
@app.post("/v1/complete")
|
| 174 |
-
async def anthropic_complete(request: Request):
|
| 175 |
-
print(f"[Main] Received /v1/complete request from {request.client.host}")
|
| 176 |
-
# 暂时重定向或提示,或者如果需要可以实现 handle_complete
|
| 177 |
-
return JSONResponse(
|
| 178 |
-
status_code=400,
|
| 179 |
-
content={"error": {"type": "invalid_request_error", "message": "KiroProxy currently only supports /v1/messages. Please check if your client can be configured to use Messages API."}}
|
| 180 |
-
)
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
# OpenAI 协议
|
| 184 |
-
@app.post("/v1/chat/completions")
|
| 185 |
-
async def openai_chat(request: Request):
|
| 186 |
-
return await openai.handle_chat_completions(request)
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
# OpenAI Responses API (Codex CLI 新版本)
|
| 190 |
-
@app.post("/v1/responses")
|
| 191 |
-
async def openai_responses(request: Request):
|
| 192 |
-
return await responses_handler.handle_responses(request)
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
# Gemini 协议
|
| 196 |
-
@app.post("/v1beta/models/{model_name}:generateContent")
|
| 197 |
-
@app.post("/v1/models/{model_name}:generateContent")
|
| 198 |
-
async def gemini_generate(model_name: str, request: Request):
|
| 199 |
-
return await gemini.handle_generate_content(model_name, request)
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
# ==================== 管理 API ====================
|
| 203 |
-
|
| 204 |
-
@app.get("/api/status")
|
| 205 |
-
async def api_status():
|
| 206 |
-
return await admin.get_status()
|
| 207 |
-
|
| 208 |
-
@app.post("/api/event_logging/batch")
|
| 209 |
-
async def api_event_logging_batch(request: Request):
|
| 210 |
-
return await admin.event_logging_batch(request)
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
@app.get("/api/stats")
|
| 214 |
-
async def api_stats():
|
| 215 |
-
return await admin.get_stats()
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
@app.get("/api/logs")
|
| 219 |
-
async def api_logs(limit: int = 100):
|
| 220 |
-
return await admin.get_logs(limit)
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
# ==================== 账号导入导出 API ====================
|
| 224 |
-
|
| 225 |
-
@app.get("/api/accounts/export")
|
| 226 |
-
async def api_export_accounts():
|
| 227 |
-
"""导出所有账号配置"""
|
| 228 |
-
return await admin.export_accounts()
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
@app.post("/api/accounts/import")
|
| 232 |
-
async def api_import_accounts(request: Request):
|
| 233 |
-
"""导入账号配置"""
|
| 234 |
-
return await admin.import_accounts(request)
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
@app.post("/api/accounts/manual")
|
| 238 |
-
async def api_add_manual_token(request: Request):
|
| 239 |
-
"""手动添加 Token"""
|
| 240 |
-
return await admin.add_manual_token(request)
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
@app.post("/api/accounts/batch")
|
| 244 |
-
async def api_batch_import_accounts(request: Request):
|
| 245 |
-
"""批量导入账号"""
|
| 246 |
-
return await admin.batch_import_accounts(request)
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
@app.post("/api/accounts/refresh-all")
|
| 250 |
-
async def api_refresh_all():
|
| 251 |
-
"""刷新所有即将过期的 token"""
|
| 252 |
-
return await admin.refresh_all_tokens()
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
# ==================== 额度管理 API (必须在 {account_id} 路由之前) ====================
|
| 256 |
-
|
| 257 |
-
@app.get("/api/accounts/status")
|
| 258 |
-
async def api_accounts_status_enhanced():
|
| 259 |
-
"""获取完整账号状态(增强版)"""
|
| 260 |
-
return await admin.get_accounts_status_enhanced()
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
@app.get("/api/accounts/summary")
|
| 264 |
-
async def api_accounts_summary():
|
| 265 |
-
"""获取账号汇总统计"""
|
| 266 |
-
return await admin.get_accounts_summary()
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
@app.post("/api/accounts/refresh-all-quotas")
|
| 270 |
-
async def api_refresh_all_quotas():
|
| 271 |
-
"""刷新所有账号额度"""
|
| 272 |
-
return await admin.refresh_all_quotas()
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
# ==================== 刷新进度 API ====================
|
| 276 |
-
|
| 277 |
-
@app.get("/api/refresh/progress")
|
| 278 |
-
async def api_refresh_progress():
|
| 279 |
-
"""获取刷新进度"""
|
| 280 |
-
return await admin.get_refresh_progress()
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
@app.post("/api/refresh/all")
|
| 284 |
-
async def api_refresh_all_with_progress():
|
| 285 |
-
"""批量刷新(带进度和锁检查)"""
|
| 286 |
-
return await admin.refresh_all_with_progress()
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
@app.get("/api/refresh/config")
|
| 290 |
-
async def api_get_refresh_config():
|
| 291 |
-
"""获取刷新配置"""
|
| 292 |
-
return await admin.get_refresh_config()
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
@app.put("/api/refresh/config")
|
| 296 |
-
async def api_update_refresh_config(request: Request):
|
| 297 |
-
"""更新刷新配置"""
|
| 298 |
-
return await admin.update_refresh_config(request)
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
@app.get("/api/refresh/status")
|
| 302 |
-
async def api_refresh_status():
|
| 303 |
-
"""获取刷新管理器状态"""
|
| 304 |
-
return await admin.get_refresh_manager_status()
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
@app.get("/api/accounts")
|
| 308 |
-
async def api_accounts():
|
| 309 |
-
return await admin.get_accounts()
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
@app.post("/api/accounts")
|
| 313 |
-
async def api_add_account(request: Request):
|
| 314 |
-
return await admin.add_account(request)
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
@app.delete("/api/accounts/{account_id}")
|
| 318 |
-
async def api_delete_account(account_id: str):
|
| 319 |
-
return await admin.delete_account(account_id)
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
@app.put("/api/accounts/{account_id}")
|
| 323 |
-
async def api_update_account(account_id: str, request: Request):
|
| 324 |
-
return await admin.update_account(account_id, request)
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
@app.post("/api/accounts/{account_id}/toggle")
|
| 328 |
-
async def api_toggle_account(account_id: str):
|
| 329 |
-
return await admin.toggle_account(account_id)
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
@app.post("/api/speedtest")
|
| 333 |
-
async def api_speedtest():
|
| 334 |
-
return await admin.speedtest()
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
@app.get("/api/accounts/{account_id}/test")
|
| 338 |
-
async def api_test_account_token(account_id: str):
|
| 339 |
-
"""测试指定账号的 Token 是否有效"""
|
| 340 |
-
return await admin.test_account_token(account_id)
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
@app.get("/api/token/scan")
|
| 344 |
-
async def api_scan_tokens():
|
| 345 |
-
return await admin.scan_tokens()
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
@app.post("/api/token/add-from-scan")
|
| 349 |
-
async def api_add_from_scan(request: Request):
|
| 350 |
-
return await admin.add_from_scan(request)
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
@app.get("/api/config/export")
|
| 354 |
-
async def api_export_config():
|
| 355 |
-
return await admin.export_config()
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
@app.post("/api/config/import")
|
| 359 |
-
async def api_import_config(request: Request):
|
| 360 |
-
return await admin.import_config(request)
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
@app.post("/api/token/refresh-check")
|
| 364 |
-
async def api_refresh_check():
|
| 365 |
-
return await admin.refresh_token_check()
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
@app.post("/api/accounts/{account_id}/refresh")
|
| 369 |
-
async def api_refresh_account(account_id: str):
|
| 370 |
-
"""刷新指定账号的 token(集成 RefreshManager)"""
|
| 371 |
-
return await admin.refresh_account_token_with_manager(account_id)
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
@app.post("/api/accounts/{account_id}/restore")
|
| 375 |
-
async def api_restore_account(account_id: str):
|
| 376 |
-
"""恢复账号(从冷却状态)"""
|
| 377 |
-
return await admin.restore_account(account_id)
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
@app.get("/api/accounts/{account_id}/usage")
|
| 381 |
-
async def api_account_usage(account_id: str):
|
| 382 |
-
"""获取账号用量信息"""
|
| 383 |
-
return await admin.get_account_usage_info(account_id)
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
@app.get("/api/accounts/{account_id}")
|
| 387 |
-
async def api_account_detail(account_id: str):
|
| 388 |
-
"""获取账号详细信息"""
|
| 389 |
-
return await admin.get_account_detail(account_id)
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
@app.post("/api/accounts/{account_id}/refresh-quota")
|
| 393 |
-
async def api_refresh_account_quota(account_id: str):
|
| 394 |
-
"""刷新单个账号额度(先刷新 Token)"""
|
| 395 |
-
return await admin.refresh_account_quota_with_token(account_id)
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
# ==================== 优先账号 API ====================
|
| 399 |
-
|
| 400 |
-
@app.get("/api/priority")
|
| 401 |
-
async def api_get_priority_accounts():
|
| 402 |
-
"""获取优先账号列表"""
|
| 403 |
-
return await admin.get_priority_accounts()
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
@app.post("/api/priority/{account_id}")
|
| 407 |
-
async def api_set_priority_account(account_id: str, request: Request):
|
| 408 |
-
"""设置优先账号"""
|
| 409 |
-
return await admin.set_priority_account(account_id, request)
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
@app.delete("/api/priority/{account_id}")
|
| 413 |
-
async def api_remove_priority_account(account_id: str):
|
| 414 |
-
"""取消优先账号"""
|
| 415 |
-
return await admin.remove_priority_account(account_id)
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
@app.put("/api/priority/reorder")
|
| 419 |
-
async def api_reorder_priority_accounts(request: Request):
|
| 420 |
-
"""调整优先账号顺序"""
|
| 421 |
-
return await admin.reorder_priority_accounts(request)
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
@app.get("/api/quota")
|
| 425 |
-
async def api_quota_status():
|
| 426 |
-
"""获取配额状态"""
|
| 427 |
-
return await admin.get_quota_status()
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
@app.get("/api/kiro/login-url")
|
| 431 |
-
async def api_login_url():
|
| 432 |
-
return await admin.get_kiro_login_url()
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
@app.get("/api/stats/detailed")
|
| 436 |
-
async def api_detailed_stats():
|
| 437 |
-
"""获取详细统计信息"""
|
| 438 |
-
return await admin.get_detailed_stats()
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
@app.post("/api/health-check")
|
| 442 |
-
async def api_health_check():
|
| 443 |
-
"""手动触发健康检查"""
|
| 444 |
-
return await admin.run_health_check()
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
@app.get("/api/browsers")
|
| 448 |
-
async def api_browsers():
|
| 449 |
-
"""获取可用浏览器列表"""
|
| 450 |
-
return await admin.get_browsers()
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
# ==================== Kiro 登录 API ====================
|
| 454 |
-
|
| 455 |
-
@app.post("/api/kiro/login/start")
|
| 456 |
-
async def api_kiro_login_start(request: Request):
|
| 457 |
-
"""启动 Kiro 设备授权登录"""
|
| 458 |
-
return await admin.start_kiro_login(request)
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
@app.get("/api/kiro/login/poll")
|
| 462 |
-
async def api_kiro_login_poll():
|
| 463 |
-
"""轮询登录状态"""
|
| 464 |
-
return await admin.poll_kiro_login()
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
@app.post("/api/kiro/login/cancel")
|
| 468 |
-
async def api_kiro_login_cancel():
|
| 469 |
-
"""取消登录"""
|
| 470 |
-
return await admin.cancel_kiro_login()
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
@app.get("/api/kiro/login/status")
|
| 474 |
-
async def api_kiro_login_status():
|
| 475 |
-
"""获取登录状态"""
|
| 476 |
-
return await admin.get_kiro_login_status()
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
# ==================== Social Auth API (Google/GitHub) ====================
|
| 480 |
-
|
| 481 |
-
@app.post("/api/kiro/social/start")
|
| 482 |
-
async def api_social_login_start(request: Request):
|
| 483 |
-
"""启动 Social Auth 登录"""
|
| 484 |
-
return await admin.start_social_login(request)
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
@app.post("/api/kiro/social/exchange")
|
| 488 |
-
async def api_social_token_exchange(request: Request):
|
| 489 |
-
"""交换 Social Auth Token"""
|
| 490 |
-
return await admin.exchange_social_token(request)
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
@app.post("/api/kiro/social/cancel")
|
| 494 |
-
async def api_social_login_cancel():
|
| 495 |
-
"""取消 Social Auth 登录"""
|
| 496 |
-
return await admin.cancel_social_login()
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
@app.get("/api/kiro/social/status")
|
| 500 |
-
async def api_social_login_status():
|
| 501 |
-
"""获取 Social Auth 状态"""
|
| 502 |
-
return await admin.get_social_login_status()
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
# ==================== 协议注册 API ====================
|
| 506 |
-
|
| 507 |
-
@app.post("/api/protocol/register")
|
| 508 |
-
async def api_register_protocol():
|
| 509 |
-
"""注册 kiro:// 协议"""
|
| 510 |
-
return await admin.register_kiro_protocol()
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
@app.post("/api/protocol/unregister")
|
| 514 |
-
async def api_unregister_protocol():
|
| 515 |
-
"""取消注册 kiro:// 协议"""
|
| 516 |
-
return await admin.unregister_kiro_protocol()
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
@app.get("/api/protocol/status")
|
| 520 |
-
async def api_protocol_status():
|
| 521 |
-
"""获取协议注册状态"""
|
| 522 |
-
return await admin.get_protocol_status()
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
@app.get("/api/protocol/callback")
|
| 526 |
-
async def api_protocol_callback():
|
| 527 |
-
"""获取回调结果"""
|
| 528 |
-
return await admin.get_callback_result()
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
# ==================== Flow Monitor API ====================
|
| 532 |
-
|
| 533 |
-
@app.get("/api/flows")
|
| 534 |
-
async def api_flows(
|
| 535 |
-
protocol: str = None,
|
| 536 |
-
model: str = None,
|
| 537 |
-
account_id: str = None,
|
| 538 |
-
state: str = None,
|
| 539 |
-
has_error: bool = None,
|
| 540 |
-
bookmarked: bool = None,
|
| 541 |
-
search: str = None,
|
| 542 |
-
limit: int = 50,
|
| 543 |
-
offset: int = 0,
|
| 544 |
-
):
|
| 545 |
-
"""查询 Flows"""
|
| 546 |
-
return await admin.get_flows(
|
| 547 |
-
protocol=protocol,
|
| 548 |
-
model=model,
|
| 549 |
-
account_id=account_id,
|
| 550 |
-
state_filter=state,
|
| 551 |
-
has_error=has_error,
|
| 552 |
-
bookmarked=bookmarked,
|
| 553 |
-
search=search,
|
| 554 |
-
limit=limit,
|
| 555 |
-
offset=offset,
|
| 556 |
-
)
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
@app.get("/api/flows/stats")
|
| 560 |
-
async def api_flow_stats():
|
| 561 |
-
"""获取 Flow 统计"""
|
| 562 |
-
return await admin.get_flow_stats()
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
@app.get("/api/flows/{flow_id}")
|
| 566 |
-
async def api_flow_detail(flow_id: str):
|
| 567 |
-
"""获取 Flow 详情"""
|
| 568 |
-
return await admin.get_flow_detail(flow_id)
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
@app.post("/api/flows/{flow_id}/bookmark")
|
| 572 |
-
async def api_bookmark_flow(flow_id: str, request: Request):
|
| 573 |
-
"""书签 Flow"""
|
| 574 |
-
return await admin.bookmark_flow(flow_id, request)
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
@app.post("/api/flows/{flow_id}/note")
|
| 578 |
-
async def api_add_flow_note(flow_id: str, request: Request):
|
| 579 |
-
"""添加 Flow 备注"""
|
| 580 |
-
return await admin.add_flow_note(flow_id, request)
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
@app.post("/api/flows/{flow_id}/tag")
|
| 584 |
-
async def api_add_flow_tag(flow_id: str, request: Request):
|
| 585 |
-
"""添加 Flow 标签"""
|
| 586 |
-
return await admin.add_flow_tag(flow_id, request)
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
@app.post("/api/flows/export")
|
| 590 |
-
async def api_export_flows(request: Request):
|
| 591 |
-
"""导出 Flows"""
|
| 592 |
-
return await admin.export_flows(request)
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
# ==================== 历史消息管理 API ====================
|
| 596 |
-
|
| 597 |
-
from .core import get_history_config, update_history_config, TruncateStrategy
|
| 598 |
-
from .core.rate_limiter import get_rate_limiter
|
| 599 |
-
|
| 600 |
-
@app.get("/api/settings/history")
|
| 601 |
-
async def api_get_history_config():
|
| 602 |
-
"""获取历史消息管理配置"""
|
| 603 |
-
config = get_history_config()
|
| 604 |
-
return config.to_dict()
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
@app.post("/api/settings/history")
|
| 608 |
-
async def api_update_history_config(request: Request):
|
| 609 |
-
"""更新历史消息管理配置"""
|
| 610 |
-
data = await request.json()
|
| 611 |
-
update_history_config(data)
|
| 612 |
-
return {"ok": True, "config": get_history_config().to_dict()}
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
# ==================== 限速配置 API ====================
|
| 616 |
-
|
| 617 |
-
@app.get("/api/settings/rate-limit")
|
| 618 |
-
async def api_get_rate_limit_config():
|
| 619 |
-
"""获取限速配置"""
|
| 620 |
-
limiter = get_rate_limiter()
|
| 621 |
-
return {
|
| 622 |
-
"enabled": limiter.config.enabled,
|
| 623 |
-
"min_request_interval": limiter.config.min_request_interval,
|
| 624 |
-
"max_requests_per_minute": limiter.config.max_requests_per_minute,
|
| 625 |
-
"global_max_requests_per_minute": limiter.config.global_max_requests_per_minute,
|
| 626 |
-
"stats": limiter.get_stats()
|
| 627 |
-
}
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
@app.post("/api/settings/rate-limit")
|
| 631 |
-
async def api_update_rate_limit_config(request: Request):
|
| 632 |
-
"""更新限速配置"""
|
| 633 |
-
data = await request.json()
|
| 634 |
-
limiter = get_rate_limiter()
|
| 635 |
-
limiter.update_config(**data)
|
| 636 |
-
return {"ok": True, "config": {
|
| 637 |
-
"enabled": limiter.config.enabled,
|
| 638 |
-
"min_request_interval": limiter.config.min_request_interval,
|
| 639 |
-
"max_requests_per_minute": limiter.config.max_requests_per_minute,
|
| 640 |
-
"global_max_requests_per_minute": limiter.config.global_max_requests_per_minute,
|
| 641 |
-
}}
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
# ==================== 文档 API ====================
|
| 645 |
-
|
| 646 |
-
# 文档标题映射
|
| 647 |
-
DOC_TITLES = {
|
| 648 |
-
"01-quickstart": "快速开始",
|
| 649 |
-
"02-features": "功能特性",
|
| 650 |
-
"03-faq": "常见问题",
|
| 651 |
-
"04-api": "API 参考",
|
| 652 |
-
"05-server-deploy": "服务器部署",
|
| 653 |
-
}
|
| 654 |
-
|
| 655 |
-
@app.get("/api/docs")
|
| 656 |
-
async def api_docs_list():
|
| 657 |
-
"""获取文档列表"""
|
| 658 |
-
docs_dir = get_resource_path("kiro_proxy/docs")
|
| 659 |
-
docs = []
|
| 660 |
-
if docs_dir.exists():
|
| 661 |
-
for doc_file in sorted(docs_dir.glob("*.md")):
|
| 662 |
-
doc_id = doc_file.stem
|
| 663 |
-
title = DOC_TITLES.get(doc_id, doc_id)
|
| 664 |
-
docs.append({"id": doc_id, "title": title})
|
| 665 |
-
return {"docs": docs}
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
@app.get("/api/docs/{doc_id}")
|
| 669 |
-
async def api_docs_content(doc_id: str):
|
| 670 |
-
"""获取文档内容"""
|
| 671 |
-
docs_dir = get_resource_path("kiro_proxy/docs")
|
| 672 |
-
doc_file = docs_dir / f"{doc_id}.md"
|
| 673 |
-
if not doc_file.exists():
|
| 674 |
-
raise HTTPException(status_code=404, detail="文档不存在")
|
| 675 |
-
content = doc_file.read_text(encoding="utf-8")
|
| 676 |
-
title = DOC_TITLES.get(doc_id, doc_id)
|
| 677 |
-
return {"id": doc_id, "title": title, "content": content}
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
# ==================== 启动 ====================
|
| 681 |
-
|
| 682 |
-
def run(port: int = 8080):
|
| 683 |
-
import uvicorn
|
| 684 |
-
print(f"\n{'='*50}")
|
| 685 |
-
print(f" Kiro API Proxy v{__version__}")
|
| 686 |
-
print(f" http://localhost:{port}")
|
| 687 |
-
print(f"{'='*50}\n")
|
| 688 |
-
uvicorn.run(app, host="0.0.0.0", port=port)
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
if __name__ == "__main__":
|
| 692 |
-
import sys
|
| 693 |
-
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
|
| 694 |
-
run(port)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
KiroProxy/legacy/kiro_proxy.py
DELETED
|
@@ -1,313 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Kiro API 反向代理服务器
|
| 4 |
-
对外暴露 OpenAI 兼容接口,内部调用 Kiro/AWS Q API
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
import json
|
| 8 |
-
import uuid
|
| 9 |
-
import os
|
| 10 |
-
import httpx
|
| 11 |
-
from fastapi import FastAPI, Request, HTTPException
|
| 12 |
-
from fastapi.responses import StreamingResponse, JSONResponse
|
| 13 |
-
import uvicorn
|
| 14 |
-
from datetime import datetime
|
| 15 |
-
from pathlib import Path
|
| 16 |
-
import logging
|
| 17 |
-
|
| 18 |
-
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 19 |
-
logger = logging.getLogger(__name__)
|
| 20 |
-
|
| 21 |
-
app = FastAPI(title="Kiro API Proxy")
|
| 22 |
-
|
| 23 |
-
# Kiro API 配置
|
| 24 |
-
KIRO_API_URL = "https://q.us-east-1.amazonaws.com/generateAssistantResponse"
|
| 25 |
-
TOKEN_PATH = Path.home() / ".aws/sso/cache/kiro-auth-token.json"
|
| 26 |
-
MACHINE_ID = "fa41d5def91e29225c73f6ea8ee0941a87bd812aae5239e3dde72c3ba7603a26"
|
| 27 |
-
|
| 28 |
-
def get_kiro_token() -> str:
|
| 29 |
-
"""从本地文件读取 Kiro token"""
|
| 30 |
-
try:
|
| 31 |
-
with open(TOKEN_PATH) as f:
|
| 32 |
-
data = json.load(f)
|
| 33 |
-
return data.get("accessToken", "")
|
| 34 |
-
except Exception as e:
|
| 35 |
-
logger.error(f"读取 token 失败: {e}")
|
| 36 |
-
raise HTTPException(status_code=500, detail="无法读取 Kiro token")
|
| 37 |
-
|
| 38 |
-
def build_kiro_headers(token: str) -> dict:
|
| 39 |
-
"""构建 Kiro API 请求头"""
|
| 40 |
-
return {
|
| 41 |
-
"content-type": "application/json",
|
| 42 |
-
"x-amzn-codewhisperer-optout": "true",
|
| 43 |
-
"x-amzn-kiro-agent-mode": "vibe",
|
| 44 |
-
"x-amz-user-agent": f"aws-sdk-js/1.0.27 KiroIDE-0.8.0-{MACHINE_ID}",
|
| 45 |
-
"user-agent": f"aws-sdk-js/1.0.27 ua/2.1 os/linux lang/js md/nodejs api/codewhispererstreaming KiroIDE-0.8.0-{MACHINE_ID}",
|
| 46 |
-
"amz-sdk-invocation-id": str(uuid.uuid4()),
|
| 47 |
-
"amz-sdk-request": "attempt=1; max=3",
|
| 48 |
-
"Authorization": f"Bearer {token}",
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
def build_kiro_request(messages: list, model: str, conversation_id: str = None) -> dict:
|
| 52 |
-
"""将 OpenAI 格式转换为 Kiro 格式"""
|
| 53 |
-
if not conversation_id:
|
| 54 |
-
conversation_id = str(uuid.uuid4())
|
| 55 |
-
|
| 56 |
-
# 提取最后一条用户消息
|
| 57 |
-
user_content = ""
|
| 58 |
-
for msg in reversed(messages):
|
| 59 |
-
if msg.get("role") == "user":
|
| 60 |
-
user_content = msg.get("content", "")
|
| 61 |
-
break
|
| 62 |
-
|
| 63 |
-
return {
|
| 64 |
-
"conversationState": {
|
| 65 |
-
"conversationId": conversation_id,
|
| 66 |
-
"currentMessage": {
|
| 67 |
-
"userInputMessage": {
|
| 68 |
-
"content": user_content,
|
| 69 |
-
"modelId": model.replace("kiro-", ""), # 移除前缀
|
| 70 |
-
"origin": "AI_EDITOR",
|
| 71 |
-
"userInputMessageContext": {}
|
| 72 |
-
}
|
| 73 |
-
},
|
| 74 |
-
"chatTriggerType": "MANUAL"
|
| 75 |
-
}
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
def parse_kiro_response(response_data: dict) -> str:
|
| 79 |
-
"""解析 Kiro 响应,提取 AI 回复内容"""
|
| 80 |
-
try:
|
| 81 |
-
# Kiro 响应格式可能是流式的,需要解析
|
| 82 |
-
if isinstance(response_data, dict):
|
| 83 |
-
# 尝试多种可能的响应路径
|
| 84 |
-
if "generateAssistantResponseResponse" in response_data:
|
| 85 |
-
resp = response_data["generateAssistantResponseResponse"]
|
| 86 |
-
if "assistantResponseEvent" in resp:
|
| 87 |
-
event = resp["assistantResponseEvent"]
|
| 88 |
-
if "content" in event:
|
| 89 |
-
return event["content"]
|
| 90 |
-
|
| 91 |
-
# 直接返回文本内容
|
| 92 |
-
if "content" in response_data:
|
| 93 |
-
return response_data["content"]
|
| 94 |
-
|
| 95 |
-
if "message" in response_data:
|
| 96 |
-
return response_data["message"]
|
| 97 |
-
|
| 98 |
-
return json.dumps(response_data)
|
| 99 |
-
except Exception as e:
|
| 100 |
-
logger.error(f"解析响应失败: {e}")
|
| 101 |
-
return str(response_data)
|
| 102 |
-
|
| 103 |
-
def parse_event_stream(raw_content: bytes) -> str:
|
| 104 |
-
"""解析 AWS event-stream 格式的响应"""
|
| 105 |
-
try:
|
| 106 |
-
# 尝试直接解码为 UTF-8
|
| 107 |
-
try:
|
| 108 |
-
text = raw_content.decode('utf-8')
|
| 109 |
-
# 如果是纯 JSON
|
| 110 |
-
if text.startswith('{'):
|
| 111 |
-
data = json.loads(text)
|
| 112 |
-
return parse_kiro_response(data)
|
| 113 |
-
except:
|
| 114 |
-
pass
|
| 115 |
-
|
| 116 |
-
# AWS event-stream 格式解析
|
| 117 |
-
# 格式: [prelude (8 bytes)][headers][payload][message CRC (4 bytes)]
|
| 118 |
-
content_parts = []
|
| 119 |
-
pos = 0
|
| 120 |
-
|
| 121 |
-
while pos < len(raw_content):
|
| 122 |
-
if pos + 12 > len(raw_content):
|
| 123 |
-
break
|
| 124 |
-
|
| 125 |
-
# 读取 prelude: total_length (4 bytes) + headers_length (4 bytes) + prelude_crc (4 bytes)
|
| 126 |
-
total_length = int.from_bytes(raw_content[pos:pos+4], 'big')
|
| 127 |
-
headers_length = int.from_bytes(raw_content[pos+4:pos+8], 'big')
|
| 128 |
-
|
| 129 |
-
if total_length == 0 or total_length > len(raw_content) - pos:
|
| 130 |
-
break
|
| 131 |
-
|
| 132 |
-
# 跳过 prelude (12 bytes) 和 headers
|
| 133 |
-
payload_start = pos + 12 + headers_length
|
| 134 |
-
payload_end = pos + total_length - 4 # 减去 message CRC
|
| 135 |
-
|
| 136 |
-
if payload_start < payload_end:
|
| 137 |
-
payload = raw_content[payload_start:payload_end]
|
| 138 |
-
try:
|
| 139 |
-
# 尝试解析 payload 为 JSON
|
| 140 |
-
payload_text = payload.decode('utf-8')
|
| 141 |
-
if payload_text.strip():
|
| 142 |
-
payload_json = json.loads(payload_text)
|
| 143 |
-
|
| 144 |
-
# 提取文本内容
|
| 145 |
-
if "assistantResponseEvent" in payload_json:
|
| 146 |
-
event = payload_json["assistantResponseEvent"]
|
| 147 |
-
if "content" in event:
|
| 148 |
-
content_parts.append(event["content"])
|
| 149 |
-
elif "content" in payload_json:
|
| 150 |
-
content_parts.append(payload_json["content"])
|
| 151 |
-
elif "text" in payload_json:
|
| 152 |
-
content_parts.append(payload_json["text"])
|
| 153 |
-
else:
|
| 154 |
-
logger.info(f" Event: {payload_text[:200]}")
|
| 155 |
-
except Exception as e:
|
| 156 |
-
logger.debug(f"解析 payload 失败: {e}")
|
| 157 |
-
|
| 158 |
-
pos += total_length
|
| 159 |
-
|
| 160 |
-
if content_parts:
|
| 161 |
-
return "".join(content_parts)
|
| 162 |
-
|
| 163 |
-
# 如果解析失败,返回原始内容的十六进制表示用于调试
|
| 164 |
-
return f"[无法解析响应,原始数据: {raw_content[:500].hex()}]"
|
| 165 |
-
|
| 166 |
-
except Exception as e:
|
| 167 |
-
logger.error(f"解析 event-stream 失败: {e}")
|
| 168 |
-
return f"[解析错误: {e}]"
|
| 169 |
-
|
| 170 |
-
@app.get("/")
|
| 171 |
-
async def root():
|
| 172 |
-
"""健康检查"""
|
| 173 |
-
token_exists = TOKEN_PATH.exists()
|
| 174 |
-
return {
|
| 175 |
-
"status": "ok",
|
| 176 |
-
"service": "Kiro API Proxy",
|
| 177 |
-
"token_available": token_exists,
|
| 178 |
-
"endpoints": {
|
| 179 |
-
"chat": "/v1/chat/completions",
|
| 180 |
-
"models": "/v1/models"
|
| 181 |
-
}
|
| 182 |
-
}
|
| 183 |
-
|
| 184 |
-
@app.get("/v1/models")
|
| 185 |
-
async def list_models():
|
| 186 |
-
"""列出可用模型 (OpenAI 兼容)"""
|
| 187 |
-
return {
|
| 188 |
-
"object": "list",
|
| 189 |
-
"data": [
|
| 190 |
-
{"id": "kiro-claude-sonnet-4", "object": "model", "owned_by": "kiro"},
|
| 191 |
-
{"id": "kiro-claude-opus-4.5", "object": "model", "owned_by": "kiro"},
|
| 192 |
-
{"id": "claude-sonnet-4", "object": "model", "owned_by": "kiro"},
|
| 193 |
-
{"id": "claude-opus-4.5", "object": "model", "owned_by": "kiro"},
|
| 194 |
-
]
|
| 195 |
-
}
|
| 196 |
-
|
| 197 |
-
@app.post("/v1/chat/completions")
|
| 198 |
-
async def chat_completions(request: Request):
|
| 199 |
-
"""OpenAI 兼容的聊天接口"""
|
| 200 |
-
try:
|
| 201 |
-
body = await request.json()
|
| 202 |
-
except:
|
| 203 |
-
raise HTTPException(status_code=400, detail="Invalid JSON")
|
| 204 |
-
|
| 205 |
-
messages = body.get("messages", [])
|
| 206 |
-
model = body.get("model", "claude-sonnet-4")
|
| 207 |
-
stream = body.get("stream", False)
|
| 208 |
-
|
| 209 |
-
if not messages:
|
| 210 |
-
raise HTTPException(status_code=400, detail="messages is required")
|
| 211 |
-
|
| 212 |
-
# 获取 token
|
| 213 |
-
token = get_kiro_token()
|
| 214 |
-
|
| 215 |
-
# 构建请求
|
| 216 |
-
headers = build_kiro_headers(token)
|
| 217 |
-
kiro_body = build_kiro_request(messages, model)
|
| 218 |
-
|
| 219 |
-
logger.info(f"📤 发送请求到 Kiro API, model={model}")
|
| 220 |
-
logger.info(f" 消息: {messages[-1].get('content', '')[:100]}...")
|
| 221 |
-
|
| 222 |
-
try:
|
| 223 |
-
async with httpx.AsyncClient(timeout=60.0, verify=False) as client:
|
| 224 |
-
response = await client.post(
|
| 225 |
-
KIRO_API_URL,
|
| 226 |
-
headers=headers,
|
| 227 |
-
json=kiro_body
|
| 228 |
-
)
|
| 229 |
-
|
| 230 |
-
logger.info(f"📥 Kiro 响应状态: {response.status_code}")
|
| 231 |
-
logger.info(f" Content-Type: {response.headers.get('content-type')}")
|
| 232 |
-
|
| 233 |
-
if response.status_code != 200:
|
| 234 |
-
logger.error(f"Kiro API 错误: {response.text}")
|
| 235 |
-
raise HTTPException(
|
| 236 |
-
status_code=response.status_code,
|
| 237 |
-
detail=f"Kiro API error: {response.text}"
|
| 238 |
-
)
|
| 239 |
-
|
| 240 |
-
# 处理响应 - 可能是 event-stream 或 JSON
|
| 241 |
-
raw_content = response.content
|
| 242 |
-
logger.info(f" 响应大小: {len(raw_content)} bytes")
|
| 243 |
-
logger.info(f" 原始响应前200字节: {raw_content[:200]}")
|
| 244 |
-
|
| 245 |
-
content = parse_event_stream(raw_content)
|
| 246 |
-
|
| 247 |
-
logger.info(f" 回复: {content[:100]}...")
|
| 248 |
-
|
| 249 |
-
# 返回 OpenAI 兼容格式
|
| 250 |
-
return JSONResponse({
|
| 251 |
-
"id": f"chatcmpl-{uuid.uuid4().hex[:8]}",
|
| 252 |
-
"object": "chat.completion",
|
| 253 |
-
"created": int(datetime.now().timestamp()),
|
| 254 |
-
"model": model,
|
| 255 |
-
"choices": [{
|
| 256 |
-
"index": 0,
|
| 257 |
-
"message": {
|
| 258 |
-
"role": "assistant",
|
| 259 |
-
"content": content
|
| 260 |
-
},
|
| 261 |
-
"finish_reason": "stop"
|
| 262 |
-
}],
|
| 263 |
-
"usage": {
|
| 264 |
-
"prompt_tokens": 0,
|
| 265 |
-
"completion_tokens": 0,
|
| 266 |
-
"total_tokens": 0
|
| 267 |
-
}
|
| 268 |
-
})
|
| 269 |
-
|
| 270 |
-
except httpx.RequestError as e:
|
| 271 |
-
logger.error(f"请求失败: {e}")
|
| 272 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 273 |
-
except Exception as e:
|
| 274 |
-
logger.error(f"未知错误: {e}")
|
| 275 |
-
import traceback
|
| 276 |
-
traceback.print_exc()
|
| 277 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 278 |
-
|
| 279 |
-
@app.get("/token/status")
|
| 280 |
-
async def token_status():
|
| 281 |
-
"""检查 token 状态"""
|
| 282 |
-
try:
|
| 283 |
-
with open(TOKEN_PATH) as f:
|
| 284 |
-
data = json.load(f)
|
| 285 |
-
expires_at = data.get("expiresAt", "unknown")
|
| 286 |
-
return {
|
| 287 |
-
"valid": True,
|
| 288 |
-
"expires_at": expires_at,
|
| 289 |
-
"path": str(TOKEN_PATH)
|
| 290 |
-
}
|
| 291 |
-
except Exception as e:
|
| 292 |
-
return {
|
| 293 |
-
"valid": False,
|
| 294 |
-
"error": str(e),
|
| 295 |
-
"path": str(TOKEN_PATH)
|
| 296 |
-
}
|
| 297 |
-
|
| 298 |
-
if __name__ == "__main__":
|
| 299 |
-
print("""
|
| 300 |
-
╔══════════════════════════════════════════════════════════════╗
|
| 301 |
-
║ Kiro API 反向代理服务器 ║
|
| 302 |
-
╠══════════════════════════════════════════════════════════════╣
|
| 303 |
-
║ 端口: 8000 ║
|
| 304 |
-
║ OpenAI 兼容接口: http://127.0.0.1:8000/v1/chat/completions ║
|
| 305 |
-
╠══════════════════════════════════════════════════════════════╣
|
| 306 |
-
║ 使用方法: ║
|
| 307 |
-
║ curl http://127.0.0.1:8000/v1/chat/completions \\ ║
|
| 308 |
-
║ -H "Content-Type: application/json" \\ ║
|
| 309 |
-
║ -d '{"model":"claude-sonnet-4","messages":[{"role":"user",║
|
| 310 |
-
║ "content":"Hello"}]}' ║
|
| 311 |
-
╚══════════════════════════════════════════════════════════════╝
|
| 312 |
-
""")
|
| 313 |
-
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
KiroProxy/requirements.txt
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
fastapi>=0.100.0
|
| 2 |
-
uvicorn>=0.23.0
|
| 3 |
-
httpx>=0.24.0
|
| 4 |
-
requests>=2.31.0
|
| 5 |
-
pytest>=7.0.0
|
| 6 |
-
hypothesis>=6.0.0
|
| 7 |
-
tiktoken>=0.5.0
|
| 8 |
-
cbor2>=5.4.0
|
| 9 |
-
|
| 10 |
-
# 数据库驱动(可选,根据DATABASE_URL自动选择)
|
| 11 |
-
asyncpg>=0.28.0 # PostgreSQL
|
| 12 |
-
aiomysql>=0.2.0 # MySQL
|
| 13 |
-
aiosqlite>=0.19.0 # SQLite
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
KiroProxy/run.py
DELETED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""Kiro API Proxy 启动脚本"""
|
| 3 |
-
import sys
|
| 4 |
-
|
| 5 |
-
if __name__ == "__main__":
|
| 6 |
-
# 如果有子命令参数,使用 CLI 模式
|
| 7 |
-
if len(sys.argv) > 1 and sys.argv[1] in ("accounts", "login", "status", "serve"):
|
| 8 |
-
from kiro_proxy.cli import main
|
| 9 |
-
main()
|
| 10 |
-
else:
|
| 11 |
-
# 兼容旧的启动方式: python run.py [port]
|
| 12 |
-
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
|
| 13 |
-
from kiro_proxy.main import run
|
| 14 |
-
run(port)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
KiroProxy/scripts/capture_kiro.py
DELETED
|
@@ -1,139 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Kiro IDE 请求抓取工具
|
| 4 |
-
|
| 5 |
-
使用 mitmproxy 抓取 Kiro IDE 发送到 AWS 的请求。
|
| 6 |
-
|
| 7 |
-
安装:
|
| 8 |
-
pip install mitmproxy
|
| 9 |
-
|
| 10 |
-
使用方法:
|
| 11 |
-
1. 运行此脚本: python capture_kiro.py
|
| 12 |
-
2. 设置系统代理为 127.0.0.1:8888
|
| 13 |
-
3. 安装 mitmproxy 的 CA 证书 (访问 http://mitm.it)
|
| 14 |
-
4. 启动 Kiro IDE 并使用
|
| 15 |
-
5. 查看 kiro_requests/ 目录下的抓取结果
|
| 16 |
-
|
| 17 |
-
或者使用 mitmproxy 命令行:
|
| 18 |
-
mitmproxy --mode regular@8888 -s capture_kiro.py
|
| 19 |
-
|
| 20 |
-
或者使用 mitmdump (无 UI):
|
| 21 |
-
mitmdump --mode regular@8888 -s capture_kiro.py
|
| 22 |
-
"""
|
| 23 |
-
|
| 24 |
-
import json
|
| 25 |
-
import os
|
| 26 |
-
from datetime import datetime
|
| 27 |
-
from mitmproxy import http, ctx
|
| 28 |
-
|
| 29 |
-
# 创建输出目录
|
| 30 |
-
OUTPUT_DIR = "kiro_requests"
|
| 31 |
-
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 32 |
-
|
| 33 |
-
# 计数器
|
| 34 |
-
request_count = 0
|
| 35 |
-
|
| 36 |
-
def request(flow: http.HTTPFlow) -> None:
|
| 37 |
-
"""处理请求"""
|
| 38 |
-
global request_count
|
| 39 |
-
|
| 40 |
-
# 只抓取 Kiro/AWS 相关请求
|
| 41 |
-
if "q.us-east-1.amazonaws.com" not in flow.request.host:
|
| 42 |
-
return
|
| 43 |
-
|
| 44 |
-
request_count += 1
|
| 45 |
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 46 |
-
|
| 47 |
-
# 保存请求
|
| 48 |
-
request_data = {
|
| 49 |
-
"timestamp": timestamp,
|
| 50 |
-
"method": flow.request.method,
|
| 51 |
-
"url": flow.request.url,
|
| 52 |
-
"headers": dict(flow.request.headers),
|
| 53 |
-
"body": None
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
# 解析请求体
|
| 57 |
-
if flow.request.content:
|
| 58 |
-
try:
|
| 59 |
-
request_data["body"] = json.loads(flow.request.content.decode('utf-8'))
|
| 60 |
-
except:
|
| 61 |
-
request_data["body_raw"] = flow.request.content.decode('utf-8', errors='replace')
|
| 62 |
-
|
| 63 |
-
# 保存到文件
|
| 64 |
-
filename = f"{OUTPUT_DIR}/{timestamp}_{request_count:04d}_request.json"
|
| 65 |
-
with open(filename, 'w', encoding='utf-8') as f:
|
| 66 |
-
json.dump(request_data, f, indent=2, ensure_ascii=False)
|
| 67 |
-
|
| 68 |
-
ctx.log.info(f"[Kiro] Captured request #{request_count}: {flow.request.method} {flow.request.path}")
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
def response(flow: http.HTTPFlow) -> None:
|
| 72 |
-
"""处理响应"""
|
| 73 |
-
# 只处理 Kiro/AWS 相关响应
|
| 74 |
-
if "q.us-east-1.amazonaws.com" not in flow.request.host:
|
| 75 |
-
return
|
| 76 |
-
|
| 77 |
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 78 |
-
|
| 79 |
-
# 保存响应
|
| 80 |
-
response_data = {
|
| 81 |
-
"timestamp": timestamp,
|
| 82 |
-
"status_code": flow.response.status_code,
|
| 83 |
-
"headers": dict(flow.response.headers),
|
| 84 |
-
"body": None
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
# 响应体可能是 event-stream 格式
|
| 88 |
-
if flow.response.content:
|
| 89 |
-
try:
|
| 90 |
-
# 尝试解析为 JSON
|
| 91 |
-
response_data["body"] = json.loads(flow.response.content.decode('utf-8'))
|
| 92 |
-
except:
|
| 93 |
-
# 保存原始内容(可能是 event-stream)
|
| 94 |
-
response_data["body_raw_length"] = len(flow.response.content)
|
| 95 |
-
# 保存前 2000 字节用于调试
|
| 96 |
-
response_data["body_preview"] = flow.response.content[:2000].decode('utf-8', errors='replace')
|
| 97 |
-
|
| 98 |
-
# 保存到文件
|
| 99 |
-
filename = f"{OUTPUT_DIR}/{timestamp}_{request_count:04d}_response.json"
|
| 100 |
-
with open(filename, 'w', encoding='utf-8') as f:
|
| 101 |
-
json.dump(response_data, f, indent=2, ensure_ascii=False)
|
| 102 |
-
|
| 103 |
-
ctx.log.info(f"[Kiro] Captured response: {flow.response.status_code}")
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
# 如果直接运行此脚本
|
| 107 |
-
if __name__ == "__main__":
|
| 108 |
-
print("""
|
| 109 |
-
╔══════════════════════════════════════════════════════════════════╗
|
| 110 |
-
║ Kiro IDE 请求抓取工具 ║
|
| 111 |
-
╠══════════════════════════════════════════════════════════════════╣
|
| 112 |
-
║ ║
|
| 113 |
-
║ 方法 1: 使用 mitmproxy (推荐) ║
|
| 114 |
-
║ ─────────────────────────────────────────────────────────────── ║
|
| 115 |
-
║ 1. 安装: pip install mitmproxy ║
|
| 116 |
-
║ 2. 运行: mitmproxy -s capture_kiro.py ║
|
| 117 |
-
║ 或: mitmdump -s capture_kiro.py ║
|
| 118 |
-
║ 3. 设置 Kiro IDE 的代理为 127.0.0.1:8080 ║
|
| 119 |
-
║ 4. 安装 CA 证书: 访问 http://mitm.it ║
|
| 120 |
-
║ ║
|
| 121 |
-
║ 方法 2: 使用 Burp Suite ║
|
| 122 |
-
║ ─────────────────────────────────────────────────────────────── ║
|
| 123 |
-
║ 1. 启动 Burp Suite ║
|
| 124 |
-
║ 2. 设置代理监听 127.0.0.1:8080 ║
|
| 125 |
-
║ 3. 导出 CA 证书并安装到系统 ║
|
| 126 |
-
║ 4. 设置 Kiro IDE 的代理 ║
|
| 127 |
-
║ ║
|
| 128 |
-
║ 方法 3: 直接修改 Kiro IDE (最简单) ║
|
| 129 |
-
║ ─────────────────────────────────────────────────────────────── ║
|
| 130 |
-
║ 在 Kiro IDE 的设置中添加: ║
|
| 131 |
-
║ "http.proxy": "http://127.0.0.1:8080" ║
|
| 132 |
-
║ ║
|
| 133 |
-
║ 或者设置环境变量: ║
|
| 134 |
-
║ export HTTPS_PROXY=http://127.0.0.1:8080 ║
|
| 135 |
-
║ export HTTP_PROXY=http://127.0.0.1:8080 ║
|
| 136 |
-
║ export NODE_TLS_REJECT_UNAUTHORIZED=0 ║
|
| 137 |
-
║ ║
|
| 138 |
-
╚══════════════════════════════════════════════════════════════════╝
|
| 139 |
-
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
KiroProxy/scripts/proxy_server.py
DELETED
|
@@ -1,135 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Kiro IDE 反向代理测试服务器
|
| 4 |
-
用于测试是否能成功拦截和转发 Kiro 的 API 请求
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
from fastapi import FastAPI, Request, Response
|
| 8 |
-
from fastapi.responses import JSONResponse, StreamingResponse
|
| 9 |
-
import httpx
|
| 10 |
-
import uvicorn
|
| 11 |
-
import json
|
| 12 |
-
import logging
|
| 13 |
-
from datetime import datetime
|
| 14 |
-
|
| 15 |
-
# 配置日志
|
| 16 |
-
logging.basicConfig(
|
| 17 |
-
level=logging.INFO,
|
| 18 |
-
format='%(asctime)s - %(levelname)s - %(message)s'
|
| 19 |
-
)
|
| 20 |
-
logger = logging.getLogger(__name__)
|
| 21 |
-
|
| 22 |
-
app = FastAPI(title="Kiro Reverse Proxy Test")
|
| 23 |
-
|
| 24 |
-
# 原始 Kiro API 地址(如果需要转发到真实服务器)
|
| 25 |
-
KIRO_API_BASE = "https://api.kiro.dev"
|
| 26 |
-
|
| 27 |
-
# 记录所有请求
|
| 28 |
-
request_log = []
|
| 29 |
-
|
| 30 |
-
@app.middleware("http")
|
| 31 |
-
async def log_requests(request: Request, call_next):
|
| 32 |
-
"""记录所有进入的请求"""
|
| 33 |
-
body = await request.body()
|
| 34 |
-
|
| 35 |
-
log_entry = {
|
| 36 |
-
"timestamp": datetime.now().isoformat(),
|
| 37 |
-
"method": request.method,
|
| 38 |
-
"url": str(request.url),
|
| 39 |
-
"path": request.url.path,
|
| 40 |
-
"headers": dict(request.headers),
|
| 41 |
-
"body": body.decode('utf-8', errors='ignore')[:2000] if body else None
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
request_log.append(log_entry)
|
| 45 |
-
logger.info(f"📥 {request.method} {request.url.path}")
|
| 46 |
-
logger.info(f" Headers: {dict(request.headers)}")
|
| 47 |
-
if body:
|
| 48 |
-
logger.info(f" Body: {body.decode('utf-8', errors='ignore')[:500]}...")
|
| 49 |
-
|
| 50 |
-
response = await call_next(request)
|
| 51 |
-
return response
|
| 52 |
-
|
| 53 |
-
@app.get("/")
|
| 54 |
-
async def root():
|
| 55 |
-
"""健康检查"""
|
| 56 |
-
return {"status": "ok", "message": "Kiro Proxy Server Running", "requests_logged": len(request_log)}
|
| 57 |
-
|
| 58 |
-
@app.get("/logs")
|
| 59 |
-
async def get_logs():
|
| 60 |
-
"""查看所有记录的请求"""
|
| 61 |
-
return {"total": len(request_log), "requests": request_log[-50:]}
|
| 62 |
-
|
| 63 |
-
@app.get("/clear")
|
| 64 |
-
async def clear_logs():
|
| 65 |
-
"""清空日志"""
|
| 66 |
-
request_log.clear()
|
| 67 |
-
return {"message": "Logs cleared"}
|
| 68 |
-
|
| 69 |
-
# 模拟认证成功响应
|
| 70 |
-
@app.api_route("/auth/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"])
|
| 71 |
-
async def mock_auth(request: Request, path: str):
|
| 72 |
-
"""模拟认证端点"""
|
| 73 |
-
logger.info(f"🔐 Auth request: {path}")
|
| 74 |
-
return JSONResponse({
|
| 75 |
-
"success": True,
|
| 76 |
-
"token": "mock-token-for-testing",
|
| 77 |
-
"expires_in": 3600
|
| 78 |
-
})
|
| 79 |
-
|
| 80 |
-
# 模拟 AI 对话端点
|
| 81 |
-
@app.post("/v1/chat/completions")
|
| 82 |
-
async def mock_chat_completions(request: Request):
|
| 83 |
-
"""模拟 OpenAI 兼容的聊天接口"""
|
| 84 |
-
body = await request.json()
|
| 85 |
-
logger.info(f"💬 Chat request: {json.dumps(body, ensure_ascii=False)[:500]}")
|
| 86 |
-
|
| 87 |
-
# 返回模拟响应
|
| 88 |
-
return JSONResponse({
|
| 89 |
-
"id": "chatcmpl-test",
|
| 90 |
-
"object": "chat.completion",
|
| 91 |
-
"created": int(datetime.now().timestamp()),
|
| 92 |
-
"model": "kiro-proxy-test",
|
| 93 |
-
"choices": [{
|
| 94 |
-
"index": 0,
|
| 95 |
-
"message": {
|
| 96 |
-
"role": "assistant",
|
| 97 |
-
"content": "🎉 反向代理测试成功!你的请求已被成功拦截。"
|
| 98 |
-
},
|
| 99 |
-
"finish_reason": "stop"
|
| 100 |
-
}],
|
| 101 |
-
"usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30}
|
| 102 |
-
})
|
| 103 |
-
|
| 104 |
-
# 捕获所有其他请求
|
| 105 |
-
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"])
|
| 106 |
-
async def catch_all(request: Request, path: str):
|
| 107 |
-
"""捕获所有其他请求并记录"""
|
| 108 |
-
body = await request.body()
|
| 109 |
-
|
| 110 |
-
logger.info(f"🎯 Caught: {request.method} /{path}")
|
| 111 |
-
|
| 112 |
-
return JSONResponse({
|
| 113 |
-
"proxy_status": "intercepted",
|
| 114 |
-
"method": request.method,
|
| 115 |
-
"path": f"/{path}",
|
| 116 |
-
"message": "请求已被反向代理捕获",
|
| 117 |
-
"headers_received": dict(request.headers)
|
| 118 |
-
})
|
| 119 |
-
|
| 120 |
-
if __name__ == "__main__":
|
| 121 |
-
print("""
|
| 122 |
-
╔══════════════════════════════════════════════════════════════╗
|
| 123 |
-
║ Kiro IDE 反向代理测试服务器 ║
|
| 124 |
-
╠══════════════════════════════════════════════════════════════╣
|
| 125 |
-
║ 端口: 8000 ║
|
| 126 |
-
║ 查看日志: http://127.0.0.1:8000/logs ║
|
| 127 |
-
║ 清空日志: http://127.0.0.1:8000/clear ║
|
| 128 |
-
╠══════════════════════════════════════════════════════════════╣
|
| 129 |
-
║ 使用方法: ║
|
| 130 |
-
║ 1. 修改 Kiro 的 JS 源码,将 api.kiro.dev 替换为 127.0.0.1:8000 ║
|
| 131 |
-
║ 2. 或者修改 /etc/hosts 添加: 127.0.0.1 api.kiro.dev ║
|
| 132 |
-
║ 3. 启动 Kiro,观察此终端的日志输出 ║
|
| 133 |
-
╚════════════════════════════���═════════════════════════════════╝
|
| 134 |
-
""")
|
| 135 |
-
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
---
|
| 2 |
title: KiroProxy
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
| 6 |
sdk: docker
|
|
@@ -10,107 +10,412 @@ short_description: Kiro IDE API 反向代理服务器 - 支持多账号管理和
|
|
| 10 |
app_port: 7860
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
## 功能特性
|
| 18 |
|
| 19 |
-
|
| 20 |
-
- **多
|
| 21 |
-
- **
|
| 22 |
-
- **
|
| 23 |
-
- **
|
| 24 |
-
- **
|
| 25 |
-
- **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
## 快速开始
|
| 28 |
|
| 29 |
-
### 本
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
```bash
|
| 32 |
# 克隆项目
|
| 33 |
-
git clone
|
| 34 |
-
cd
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
# 安装依赖
|
| 37 |
pip install -r requirements.txt
|
| 38 |
|
| 39 |
-
#
|
| 40 |
-
python run.py
|
|
|
|
|
|
|
|
|
|
| 41 |
```
|
| 42 |
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
```bash
|
| 46 |
-
#
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
```
|
| 55 |
|
| 56 |
-
###
|
| 57 |
|
| 58 |
-
|
| 59 |
-
2. 在 Space 设置中配置环境��量:
|
| 60 |
-
- `ADMIN_PASSWORD`: 管理员密码
|
| 61 |
-
- `DATABASE_URL`: 远程数据库连接(可选)
|
| 62 |
-
3. Space 将自动构建和部署
|
| 63 |
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
-
|
|
|
|
|
|
|
| 67 |
|
| 68 |
-
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
| 73 |
|
| 74 |
-
##
|
| 75 |
|
| 76 |
-
|
| 77 |
|
| 78 |
-
|
| 79 |
-
- 如果设置了 `ADMIN_PASSWORD` 环境变量,使用该密码登录
|
| 80 |
-
- 如果未设置,系统会自动生成随机密码并在启动日志中显示
|
| 81 |
|
| 82 |
-
##
|
| 83 |
|
| 84 |
-
|
| 85 |
-
- 配置存储在 `~/.kiro-proxy/` 目录
|
| 86 |
-
- 适合本地开发和小规模部署
|
| 87 |
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
-
|
| 94 |
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
```
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
```
|
| 99 |
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
```
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
```
|
| 104 |
|
| 105 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
```
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
```
|
| 109 |
|
| 110 |
-
|
| 111 |
|
| 112 |
-
|
| 113 |
|
| 114 |
-
|
| 115 |
|
| 116 |
-
|
|
|
|
| 1 |
---
|
| 2 |
title: KiroProxy
|
| 3 |
+
emoji: ??
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
| 6 |
sdk: docker
|
|
|
|
| 10 |
app_port: 7860
|
| 11 |
---
|
| 12 |
|
| 13 |
+
<p align="center">
|
| 14 |
+
<img src="assets/icon.svg" width="80" height="96" alt="Kiro Proxy">
|
| 15 |
+
</p>
|
| 16 |
|
| 17 |
+
<h1 align="center">Kiro API Proxy</h1>
|
| 18 |
+
|
| 19 |
+
<p align="center">
|
| 20 |
+
Kiro IDE API 反向代理服务器,支持多账号轮询、Token 自动刷新、配额管理
|
| 21 |
+
</p>
|
| 22 |
+
|
| 23 |
+
<p align="center">
|
| 24 |
+
<a href="#功能特性">功能</a> ?
|
| 25 |
+
<a href="#快速开始">快速开始</a> ?
|
| 26 |
+
<a href="#cli-配置">CLI 配置</a> ?
|
| 27 |
+
<a href="#api-端点">API</a> ?
|
| 28 |
+
<a href="#许可证">许可证</a>
|
| 29 |
+
</p>
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
|
| 33 |
+
> **?? 测试说明**
|
| 34 |
+
>
|
| 35 |
+
> 本项目支持 **Claude Code**、**Codex CLI**、**Gemini CLI** 三种客户端,工具调用功能已全面支持。
|
| 36 |
|
| 37 |
## 功能特性
|
| 38 |
|
| 39 |
+
### 核心功能
|
| 40 |
+
- **多协议支持** - OpenAI / Anthropic / Gemini 三种协议兼容
|
| 41 |
+
- **完整工具调用** - 三种协议的工具调用功能全面支持
|
| 42 |
+
- **图片理解** - 支持 Claude Code / Codex CLI 图片输入
|
| 43 |
+
- **网络搜索** - 支持 Claude Code / Codex CLI 网络搜索工具
|
| 44 |
+
- **思考功能** - 支持 Claude 的扩展思考功能(Extended Thinking)
|
| 45 |
+
- **多账号轮询(默认随机)** - 每次请求随机切换账号,分散压力,避免单账号 RPM 过高
|
| 46 |
+
- **会话粘性(可选)** - 非 `random` 策略下,同一会话 60 秒内使用同一账号,保持上下文
|
| 47 |
+
- **Web UI** - 简洁的管理界面,支持监控、日志、设置
|
| 48 |
+
|
| 49 |
+
### v1.7.1 新功能
|
| 50 |
+
- **Windows 支持补强** - 注册表浏览器检测 + PATH 回退,兼容便携版
|
| 51 |
+
- **打包资源修复** - PyInstaller 打包后可正常加载图标与内置文档
|
| 52 |
+
- **Token 扫描稳定性** - Windows 路径编码处理修复
|
| 53 |
+
|
| 54 |
+
### v1.6.3 新功能
|
| 55 |
+
- **命令行工具 (CLI)** - 无 GUI 服务器也能轻松管理
|
| 56 |
+
- `python run.py accounts list` - 列出账号
|
| 57 |
+
- `python run.py accounts export/import` - 导出/导入账号
|
| 58 |
+
- `python run.py accounts add` - 交互式添加 Token
|
| 59 |
+
- `python run.py accounts scan` - 扫描本地 Token
|
| 60 |
+
- `python run.py login google/github` - 命令行登录
|
| 61 |
+
- `python run.py login remote` - 生成远程登录链接
|
| 62 |
+
- **远程登录链接** - 在有浏览器的机器上完成授权,Token 自动同步
|
| 63 |
+
- **账号导入导出** - 跨机器迁移账号配置
|
| 64 |
+
- **手动添加 Token** - 直接粘贴 accessToken/refreshToken
|
| 65 |
+
|
| 66 |
+
### v1.6.2 新功能
|
| 67 |
+
- **Codex CLI 完整支持** - 使用 OpenAI Responses API (`/v1/responses`)
|
| 68 |
+
- 完整工具调用支持(shell、file 等所有工具)
|
| 69 |
+
- 图片输入支持(`input_image` 类型)
|
| 70 |
+
- 网络搜索支持(`web_search` 工具)
|
| 71 |
+
- 错误代码映射(rate_limit、context_length 等)
|
| 72 |
+
- **Claude Code 增强** - 图片理解和网络搜索完整支持
|
| 73 |
+
- 支持 Anthropic 和 OpenAI 两种图片格式
|
| 74 |
+
- 支持 `web_search` / `web_search_20250305` 工具
|
| 75 |
+
|
| 76 |
+
### v1.6.1 新功能
|
| 77 |
+
- **请求限速** - 通过限制请求频率降低账号封禁风险
|
| 78 |
+
- 每账号最小请求间隔
|
| 79 |
+
- 每账号每分钟最大请求数
|
| 80 |
+
- 全局每分钟最大请求数
|
| 81 |
+
- WebUI 设置页面可配置
|
| 82 |
+
- **账号封禁检测** - 自动检测 TEMPORARILY_SUSPENDED 错误
|
| 83 |
+
- 友好的错误日志输出
|
| 84 |
+
- 自动禁用被封禁账号
|
| 85 |
+
- 自动切换到其他可用账号
|
| 86 |
+
- **统一错误处理** - 三种协议使用统一的错误分类和处理
|
| 87 |
+
|
| 88 |
+
### v1.6.0 功能
|
| 89 |
+
- **历史消息管理** - 4 种策略处理对话长度限制,可自由组合
|
| 90 |
+
- 自动截断:发送前优先保留最新上下文并摘要前文,必要时按数量/字符数截断
|
| 91 |
+
- 智能摘要:用 AI 生成早期对话摘要,保留关键信息
|
| 92 |
+
- 摘要缓存:历史变化不大时复用最近摘要,减少重复 LLM 调用(默认启用)
|
| 93 |
+
- 错误重试:遇到长度错误时自动截断重试(默认启用)
|
| 94 |
+
- 预估检测:预估 token 数量,超限预先截断
|
| 95 |
+
- **Gemini 工具调用** - 完整支持 functionDeclarations/functionCall/functionResponse
|
| 96 |
+
- **设置页面** - WebUI 新增设置标签页,可配置历史消息管理策略
|
| 97 |
+
|
| 98 |
+
### v1.5.0 功能
|
| 99 |
+
- **用量查询** - 查询账号配额使用情况,显示已用/余额/使用率
|
| 100 |
+
- **多登录方式** - 支持 Google / GitHub / AWS Builder ID 三种登录方式
|
| 101 |
+
- **流量监控** - 完整的 LLM 请求监控,支持搜索、过滤、导出
|
| 102 |
+
- **浏览器选择** - 自动检测已安��浏览器,支持无痕模式
|
| 103 |
+
- **文档中心** - 内置帮助文档,左侧目录 + 右侧 Markdown 渲染
|
| 104 |
+
|
| 105 |
+
### v1.4.0 功能
|
| 106 |
+
- **Token 预刷新** - 后台每 5 分钟检查,提前 15 分钟自动刷新
|
| 107 |
+
- **健康检查** - 每 10 分钟检测账号可用性,自动标记状态
|
| 108 |
+
- **请求统计增强** - 按账号/模型统计,24 小时趋势
|
| 109 |
+
- **请求重试机制** - 网络错误/5xx 自动重试,指数退避
|
| 110 |
+
|
| 111 |
+
## 工具调用支持
|
| 112 |
+
|
| 113 |
+
| 功能 | Anthropic (Claude Code) | OpenAI (Codex CLI) | Gemini |
|
| 114 |
+
|------|------------------------|-------------------|--------|
|
| 115 |
+
| 工具定义 | ? `tools` | ? `tools.function` | ? `functionDeclarations` |
|
| 116 |
+
| 工具调用响应 | ? `tool_use` | ? `tool_calls` | ? `functionCall` |
|
| 117 |
+
| 工具结果 | ? `tool_result` | ? `tool` 角色消息 | ? `functionResponse` |
|
| 118 |
+
| 强制工具调用 | ? `tool_choice` | ? `tool_choice` | ? `toolConfig.mode` |
|
| 119 |
+
| 工具数量限制 | ? 50 个 | ? 50 个 | ? 50 个 |
|
| 120 |
+
| 历史消息修复 | ? | ? | ? |
|
| 121 |
+
| 图片理解 | ? | ? | ? |
|
| 122 |
+
| 网络搜索 | ? | ? | ? |
|
| 123 |
+
|
| 124 |
+
## 已知限制
|
| 125 |
+
|
| 126 |
+
### 对话长度限制
|
| 127 |
+
|
| 128 |
+
Kiro API 有输入长度限制。当对话历史过长时,会返回错误:
|
| 129 |
+
|
| 130 |
+
```
|
| 131 |
+
Input is too long. (CONTENT_LENGTH_EXCEEDS_THRESHOLD)
|
| 132 |
+
```
|
| 133 |
+
|
| 134 |
+
#### 自动处理(v1.6.0+)
|
| 135 |
+
|
| 136 |
+
代理内置了历史消息管理功能,可在「设置」页面配置:
|
| 137 |
+
|
| 138 |
+
- **错误重试**(默认):遇到长度错误时自动截断并重试
|
| 139 |
+
- **智能摘要**:用 AI 生成早期对话摘要,保留关键信息
|
| 140 |
+
- **摘要缓存**(默认):历史变化不大时复用最近摘要,减少重复 LLM 调用
|
| 141 |
+
- **自动截断**:每次请求前优先保留最新上下文并摘要前文,必要时按数量/字符数截断
|
| 142 |
+
- **预估检测**:预估 token 数量,超限预先截断
|
| 143 |
+
|
| 144 |
+
如需 **关闭自动压缩/重试**(超限时直接报错),可设置环境变量 `KIROPROXY_HISTORY_ERROR_RETRY=0`,或将历史配置的 `strategies` 中移除 `error_retry`。
|
| 145 |
+
|
| 146 |
+
摘要缓存可通过以下配置项调整(默认值):
|
| 147 |
+
- `summary_cache_enabled`: `true`
|
| 148 |
+
- `summary_cache_min_delta_messages`: `3`
|
| 149 |
+
- `summary_cache_min_delta_chars`: `4000`
|
| 150 |
+
- `summary_cache_max_age_seconds`: `180`
|
| 151 |
+
|
| 152 |
+
#### 手动处理
|
| 153 |
+
|
| 154 |
+
1. 在 Claude Code 中输入 `/clear` 清空对话历史
|
| 155 |
+
2. 告诉 AI 你之前在做什么,它会读取代码文件恢复上下文
|
| 156 |
|
| 157 |
## 快速开始
|
| 158 |
|
| 159 |
+
### 方式一:下载预编译版本
|
| 160 |
+
|
| 161 |
+
从 [Releases](../../releases) 下载对应平台的安装包,解压后直接运行。
|
| 162 |
+
|
| 163 |
+
### 方式二:从源码运行
|
| 164 |
|
| 165 |
```bash
|
| 166 |
# 克隆项目
|
| 167 |
+
git clone https://github.com/yourname/kiro-proxy.git
|
| 168 |
+
cd kiro-proxy
|
| 169 |
+
|
| 170 |
+
# 创建虚拟环境
|
| 171 |
+
python -m venv venv
|
| 172 |
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
| 173 |
|
| 174 |
# 安装依赖
|
| 175 |
pip install -r requirements.txt
|
| 176 |
|
| 177 |
+
# 运行
|
| 178 |
+
python run.py
|
| 179 |
+
|
| 180 |
+
# 或指定端口
|
| 181 |
+
python run.py 8081
|
| 182 |
```
|
| 183 |
|
| 184 |
+
启动后访问 http://localhost:8080
|
| 185 |
+
|
| 186 |
+
### 命令行工具 (CLI)
|
| 187 |
+
|
| 188 |
+
无 GUI 服务器可使用 CLI 管理账号:
|
| 189 |
|
| 190 |
```bash
|
| 191 |
+
# 账号管理
|
| 192 |
+
python run.py accounts list # 列出账号
|
| 193 |
+
python run.py accounts export -o acc.json # 导出账号
|
| 194 |
+
python run.py accounts import acc.json # 导入账号
|
| 195 |
+
python run.py accounts add # 交互式添加 Token
|
| 196 |
+
python run.py accounts scan --auto # 扫描并自动添加本地 Token
|
| 197 |
+
|
| 198 |
+
# 登录
|
| 199 |
+
python run.py login google # Google 登录
|
| 200 |
+
python run.py login github # GitHub 登录
|
| 201 |
+
python run.py login remote --host myserver.com:8080 # 生成远程登录链接
|
| 202 |
+
|
| 203 |
+
# 服务
|
| 204 |
+
python run.py serve # 启动服务 (默认 8080)
|
| 205 |
+
python run.py serve -p 8081 # 指定端口
|
| 206 |
+
python run.py status # 查看状态
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
### 登录获取 Token
|
| 210 |
+
|
| 211 |
+
**方式一:在线登录(推荐)**
|
| 212 |
+
1. 打开 Web UI,点击「在线登录」
|
| 213 |
+
2. 选择登录方式:Google / GitHub / AWS Builder ID
|
| 214 |
+
3. 在浏览器中完成授权
|
| 215 |
+
4. 账号自动添加
|
| 216 |
+
|
| 217 |
+
**方式二:扫描 Token**
|
| 218 |
+
1. 打开 Kiro IDE,使用 Google/GitHub 账号登录
|
| 219 |
+
2. 登录成功后 token 自动保存到 `~/.aws/sso/cache/`
|
| 220 |
+
3. 在 Web UI 点击「扫描 Token」添加账号
|
| 221 |
+
|
| 222 |
+
## CLI 配置
|
| 223 |
+
|
| 224 |
+
### 模型对照表
|
| 225 |
+
|
| 226 |
+
| Kiro 模型 | 能力 | Claude Code | Codex |
|
| 227 |
+
|-----------|------|-------------|-------|
|
| 228 |
+
| `claude-sonnet-4` | ??? 推荐 | `claude-sonnet-4` | `gpt-4o` |
|
| 229 |
+
| `claude-sonnet-4.5` | ???? 更强 | `claude-sonnet-4.5` | `gpt-4o` |
|
| 230 |
+
| `claude-haiku-4.5` | ? 快速 | `claude-haiku-4.5` | `gpt-4o-mini` |
|
| 231 |
+
|
| 232 |
+
### Claude Code 配置
|
| 233 |
|
| 234 |
+
```
|
| 235 |
+
名称: Kiro Proxy
|
| 236 |
+
API Key: any
|
| 237 |
+
Base URL: http://localhost:8080
|
| 238 |
+
模型: claude-sonnet-4
|
| 239 |
```
|
| 240 |
|
| 241 |
+
### Codex 配置
|
| 242 |
|
| 243 |
+
Codex CLI 使用 OpenAI Responses API,配置如下:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
|
| 245 |
+
```bash
|
| 246 |
+
# 设置环境变量
|
| 247 |
+
export OPENAI_API_KEY=any
|
| 248 |
+
export OPENAI_BASE_URL=http://localhost:8080/v1
|
| 249 |
|
| 250 |
+
# 运行 Codex
|
| 251 |
+
codex
|
| 252 |
+
```
|
| 253 |
|
| 254 |
+
或在 `~/.codex/config.toml` 中配置:
|
| 255 |
|
| 256 |
+
```toml
|
| 257 |
+
[providers.openai]
|
| 258 |
+
api_key = "any"
|
| 259 |
+
base_url = "http://localhost:8080/v1"
|
| 260 |
+
```
|
| 261 |
|
| 262 |
+
## 思考功能支持
|
| 263 |
|
| 264 |
+
### 什么是思考功能
|
| 265 |
|
| 266 |
+
思考功能(Extended Thinking)允许 Claude 在生成回答前展示其思考过程,帮助用户理解 AI 的推理步骤。
|
|
|
|
|
|
|
| 267 |
|
| 268 |
+
### 如何使用
|
| 269 |
|
| 270 |
+
在请求中添加 `thinking`(或对应协议的 thinking 配置)即可启用:
|
|
|
|
|
|
|
| 271 |
|
| 272 |
+
```json
|
| 273 |
+
{
|
| 274 |
+
"model": "claude-sonnet-4.5",
|
| 275 |
+
"messages": [
|
| 276 |
+
{
|
| 277 |
+
"role": "user",
|
| 278 |
+
"content": "解释一下量子计算的原理"
|
| 279 |
+
}
|
| 280 |
+
],
|
| 281 |
+
"thinking": {
|
| 282 |
+
"thinking_type": "enabled",
|
| 283 |
+
"budget_tokens": 20000
|
| 284 |
+
},
|
| 285 |
+
"stream": true
|
| 286 |
+
}
|
| 287 |
+
```
|
| 288 |
|
| 289 |
+
OpenAI Chat Completions (`POST /v1/chat/completions`) 也支持:
|
| 290 |
|
| 291 |
+
```json
|
| 292 |
+
{
|
| 293 |
+
"model": "gpt-4o",
|
| 294 |
+
"messages": [{"role": "user", "content": "解释一下量子计算的原理"}],
|
| 295 |
+
"thinking": { "type": "enabled" },
|
| 296 |
+
"stream": true
|
| 297 |
+
}
|
| 298 |
```
|
| 299 |
+
|
| 300 |
+
OpenAI Responses (`POST /v1/responses`) 也支持:
|
| 301 |
+
|
| 302 |
+
```json
|
| 303 |
+
{
|
| 304 |
+
"model": "gpt-4o",
|
| 305 |
+
"input": "解释一下量子计算的原理",
|
| 306 |
+
"thinking": { "type": "enabled" }
|
| 307 |
+
}
|
| 308 |
```
|
| 309 |
|
| 310 |
+
Gemini generateContent (`POST /v1/models/{model}:generateContent`) 也支持:
|
| 311 |
+
|
| 312 |
+
```json
|
| 313 |
+
{
|
| 314 |
+
"contents": [{"role": "user", "parts": [{"text": "解释一下量子计算的原理"}]}],
|
| 315 |
+
"generationConfig": {
|
| 316 |
+
"thinkingConfig": { "includeThoughts": true }
|
| 317 |
+
}
|
| 318 |
+
}
|
| 319 |
```
|
| 320 |
+
|
| 321 |
+
### 参数说明
|
| 322 |
+
|
| 323 |
+
- `thinking_type`: 思考类型,设为 `"enabled"` 启用思考功能
|
| 324 |
+
- `budget_tokens`: 思考过程的 token 预算(不传则视为无限制)
|
| 325 |
+
|
| 326 |
+
### 响应格式
|
| 327 |
+
|
| 328 |
+
启用思考功能后,流式响应会包含两种内容块:
|
| 329 |
+
|
| 330 |
+
1. **思考块**(type: "thinking"):展示 AI 的思考过程
|
| 331 |
+
2. **文本块**(type: "text"):最终的回答内容
|
| 332 |
+
|
| 333 |
+
示例响应:
|
| 334 |
+
```
|
| 335 |
+
data: {"type":"content_block_start","index":1,"content_block":{"type":"thinking","thinking":""}}
|
| 336 |
+
data: {"type":"content_block_delta","index":1,"delta":{"type":"thinking_delta","thinking":"让我思考一下量子计算的原理..."}}
|
| 337 |
+
data: {"type":"content_block_stop","index":1}
|
| 338 |
+
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
|
| 339 |
+
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"量子计算是一种..."}}
|
| 340 |
+
data: {"type":"content_block_stop","index":0}
|
| 341 |
```
|
| 342 |
|
| 343 |
+
## API 端点
|
| 344 |
+
|
| 345 |
+
| 协议 | 端点 | 用途 |
|
| 346 |
+
|------|------|------|
|
| 347 |
+
| OpenAI | `POST /v1/chat/completions` | Chat Completions API |
|
| 348 |
+
| OpenAI | `POST /v1/responses` | Responses API (Codex CLI) |
|
| 349 |
+
| OpenAI | `GET /v1/models` | 模型列表 |
|
| 350 |
+
| Anthropic | `POST /v1/messages` | Claude Code |
|
| 351 |
+
| Anthropic | `POST /v1/messages/count_tokens` | Token 计数 |
|
| 352 |
+
| Gemini | `POST /v1/models/{model}:generateContent` | Gemini CLI |
|
| 353 |
+
|
| 354 |
+
### 管理 API
|
| 355 |
+
|
| 356 |
+
| 端点 | 方法 | 说明 |
|
| 357 |
+
|------|------|------|
|
| 358 |
+
| `/api/accounts` | GET | 获取所有账号状态 |
|
| 359 |
+
| `/api/accounts/{id}` | GET | 获取账号详情 |
|
| 360 |
+
| `/api/accounts/{id}/usage` | GET | 获取账号用量信息 |
|
| 361 |
+
| `/api/accounts/{id}/refresh` | POST | 刷新账号 Token |
|
| 362 |
+
| `/api/accounts/{id}/restore` | POST | 恢复账号(从冷却状态) |
|
| 363 |
+
| `/api/accounts/refresh-all` | POST | 刷新所有即将过期的 Token |
|
| 364 |
+
| `/api/flows` | GET | 获取流量记录 |
|
| 365 |
+
| `/api/flows/stats` | GET | 获取流量统计 |
|
| 366 |
+
| `/api/flows/{id}` | GET | 获取流量详情 |
|
| 367 |
+
| `/api/quota` | GET | 获取配额状态 |
|
| 368 |
+
| `/api/stats` | GET | 获取统计信息 |
|
| 369 |
+
| `/api/health-check` | POST | 手动触发健康检查 |
|
| 370 |
+
| `/api/browsers` | GET | 获取可用浏览器列表 |
|
| 371 |
+
| `/api/docs` | GET | 获取文档列表 |
|
| 372 |
+
| `/api/docs/{id}` | GET | 获取文档内容 |
|
| 373 |
+
|
| 374 |
+
## 项目结构
|
| 375 |
+
|
| 376 |
+
```
|
| 377 |
+
.
|
| 378 |
+
├── run.py
|
| 379 |
+
├── build.py
|
| 380 |
+
├── pyproject.toml
|
| 381 |
+
├── requirements.txt
|
| 382 |
+
├── kiro_proxy/
|
| 383 |
+
│ ├── main.py # FastAPI 应用入口
|
| 384 |
+
│ ├── config.py # 全局配置
|
| 385 |
+
│ ├── converters/ # 协议转换
|
| 386 |
+
│ ├── core/ # 核心模块
|
| 387 |
+
│ ├── credential/ # 凭证管理
|
| 388 |
+
│ ├── auth/ # 认证模块
|
| 389 |
+
│ ├── handlers/ # API 处理器
|
| 390 |
+
│ │ ├── anthropic/ # /v1/messages
|
| 391 |
+
│ │ ├── admin/ # 管理 API
|
| 392 |
+
│ │ ├── openai.py # /v1/chat/completions
|
| 393 |
+
│ │ ├── responses.py # /v1/responses (Codex CLI)
|
| 394 |
+
│ │ └── gemini.py # /v1/models/{model}:generateContent
|
| 395 |
+
│ ├── routers/ # 路由层
|
| 396 |
+
│ ├── web/ # Web UI
|
| 397 |
+
│ └── docs/ # 内置文档
|
| 398 |
+
├── assets/ # 资源文件
|
| 399 |
+
├── legacy/ # 兼容旧入口
|
| 400 |
+
├── scripts/ # 辅助脚本
|
| 401 |
+
├── examples/ # 示例
|
| 402 |
+
└── tests/ # 测试
|
| 403 |
```
|
| 404 |
+
|
| 405 |
+
## 构建
|
| 406 |
+
|
| 407 |
+
```bash
|
| 408 |
+
# 安装构建依赖
|
| 409 |
+
pip install pyinstaller
|
| 410 |
+
|
| 411 |
+
# 构建
|
| 412 |
+
python build.py
|
| 413 |
```
|
| 414 |
|
| 415 |
+
输出文件在 `dist/` 目录。
|
| 416 |
|
| 417 |
+
## 免责声明
|
| 418 |
|
| 419 |
+
本项目仅供学习研究,禁止商用。使用本项目产生的任何后果由使用者自行承担,与作者无关。
|
| 420 |
|
| 421 |
+
本项目与 Kiro / AWS / Anthropic 官方无关。
|
README_HF.md
DELETED
|
@@ -1,9 +0,0 @@
|
|
| 1 |
-
title: KiroProxy
|
| 2 |
-
emoji: 🚀
|
| 3 |
-
colorFrom: blue
|
| 4 |
-
colorTo: purple
|
| 5 |
-
sdk: docker
|
| 6 |
-
pinned: false
|
| 7 |
-
license: mit
|
| 8 |
-
short_description: Kiro IDE API 反向代理服务器 - 支持多账号管理和多协议转换
|
| 9 |
-
app_port: 7860
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{KiroProxy/assets → assets}/icon.iconset/icon_128x128.png
RENAMED
|
File without changes
|
{KiroProxy/assets → assets}/icon.iconset/icon_16x16.png
RENAMED
|
File without changes
|
{KiroProxy/assets → assets}/icon.iconset/icon_256x256.png
RENAMED
|
File without changes
|
{KiroProxy/assets → assets}/icon.iconset/icon_32x32.png
RENAMED
|
File without changes
|
{KiroProxy/assets → assets}/icon.iconset/icon_512x512.png
RENAMED
|
File without changes
|
{KiroProxy/assets → assets}/icon.iconset/icon_64x64.png
RENAMED
|
File without changes
|
{KiroProxy/assets → assets}/icon.png
RENAMED
|
File without changes
|
{KiroProxy/assets → assets}/icon.svg
RENAMED
|
File without changes
|
KiroProxy/build.py → build.py
RENAMED
|
File without changes
|
{KiroProxy/examples → examples}/quota_display_example.py
RENAMED
|
File without changes
|
{KiroProxy/examples → examples}/test_quota_display.html
RENAMED
|
File without changes
|
KiroProxy/kiro.svg → kiro.svg
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/__init__.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/__main__.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/auth/__init__.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/auth/device_flow.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/cli.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/config.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/converters/__init__.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/__init__.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/account.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/account_selector.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/admin_auth.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/auth_middleware.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/browser.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/database.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/error_handler.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/flow_monitor.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/history_manager.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/kiro_api.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/persistence.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/protocol_handler.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/quota_cache.py
RENAMED
|
File without changes
|
{KiroProxy/kiro_proxy → kiro_proxy}/core/quota_scheduler.py
RENAMED
|
File without changes
|