feat: 添加防截断模型支持和错误处理优化
1. 在gemini-core.js中实现anti-truncation模型支持,通过流式续写解决长文本截断问题 2. 增强common.js中的错误处理,统一返回错误响应格式 3. 更新README文档,优化描述并添加Claude Sonnet 4.5支持 4. 调整.gitignore,添加新的提示词文件 5. 优化convert.js中的默认参数和模型映射逻辑 6. 在claude-kiro.js中添加Claude Sonnet 4.5模型支持
This commit is contained in:
parent
76b115f35f
commit
7a483e49b5
8 changed files with 229 additions and 80 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -3,4 +3,6 @@ node_modules
|
|||
.claude/
|
||||
CLAUDE.md
|
||||
config.json
|
||||
provider_pools.json
|
||||
provider_pools.json
|
||||
fetch_system_prompt.txt
|
||||
input_system_prompt.txt
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
* ✅ **Unified Access to Multiple Models**: One interface to access Gemini, OpenAI, Claude, Kimi K2, GLM-4.5, Qwen Code, and other cutting-edge models. Freely switch between different model providers using simple startup parameters or request headers.
|
||||
* ✅ **Bypass Official Restrictions**: By supporting Gemini CLI's OAuth authorization method, it effectively circumvents the rate and quota limits of official free APIs, granting you higher request quotas and usage frequency.
|
||||
* ✅ **Bypass Client Restrictions**: Kiro API mode supports free usage of the Claude Sonnet 4 model.
|
||||
* ✅ **Bypass Client Restrictions**: Kiro API mode supports free usage of the Claude Sonnet 4.5 model.
|
||||
* ✅ **Seamless OpenAI Compatibility**: Provides an interface fully compatible with the OpenAI API, enabling your existing toolchains and clients (e.g., LobeChat, NextChat) to integrate all supported models at zero cost.
|
||||
* ✅ **Intelligent Account Pool Management**: Supports multi-account polling, failover, and configuration degradation, ensuring high service availability and effectively avoiding single account limitations.
|
||||
* ✅ **Enhanced Controllability**: Powerful logging features allow you to capture and record all request prompts, facilitating auditing, debugging, and building private datasets.
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
* ✅ **マルチモデル統一アクセス**:一つのインターフェースで、Gemini、OpenAI、Claude、Kimi K2、GLM-4.5、Qwen Codeなど複数の最新モデルに対応。シンプルな起動パラメータやリクエストヘッダーで、異なるモデルプロバイダー間を自由に切り替え可能。
|
||||
* ✅ **公式制限の突破**:Gemini CLIのOAuth認証方式をサポートすることで、公式無料APIのレート制限と割り当て制限を効果的に回避し、より高いリクエスト枠と使用頻度を享受。
|
||||
* ✅ **クライアント制限の突破**:Kiro APIモードでClaude Sonnet 4モデルの無料使用をサポート。
|
||||
* ✅ **クライアント制限の突破**:Kiro APIモードでClaude Sonnet 4.5モデルの無料使用をサポート。
|
||||
* ✅ **OpenAIとのシームレスな互換性**:OpenAI APIと完全に互換性のあるインターフェースを提供し、既存のツールチェーンとクライアント(LobeChat、NextChatなど)がゼロコストですべてのサポートモデルに接続可能。
|
||||
* ✅ **アカウントプールのインテリジェント管理**:マルチアカウントのラウンドロビン、フェイルオーバー、設定ダウングレードをサポートし、サービスの高可用性を確保し、単一アカウントの制限問題を効果的に回避。
|
||||
* ✅ **強化された制御性**:強力なログ機能により、すべてのリクエストのプロンプトをキャプチャして記録でき、監査、デバッグ、プライベートデータセット構築に便利。
|
||||
|
|
|
|||
125
README.md
125
README.md
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
</div>
|
||||
|
||||
`AIClient2API` 是一个专为开发者打造的多功能、轻量化 API 代理,旨在提供大量免费的 API 请求额度,全面支持 Gemini、Qwen Code、Claude 等主流大模型。通过一个 Node.js HTTP 服务器,它将多种后端 API 统一转换为标准的 OpenAI 格式接口。项目采用现代化的模块化架构,支持策略模式和适配器模式,具备完整的测试覆盖和健康检查机制,开箱即用,`npm install` 后即可直接运行。您只需在配置文件中轻松切换模型服务商,就能让任何兼容 OpenAI 的客户端或应用,通过同一个 API 地址,无缝地使用不同的大模型能力,彻底摆脱为不同服务维护多套配置和处理接口不兼容问题的烦恼。
|
||||
`AIClient2API` 是一个多功能、轻量化的 API 代理,旨在将多种大模型客户端(如 Gemini CLI, Qwen Code Plus, Kiro Claude 等)模拟为本地 OpenAI 兼容接口。它通过一个 Node.js HTTP 服务器,将不同模型的后端 API 统一转换为标准的 OpenAI 格式,让任何兼容 OpenAI 的客户端或应用,通过一个 API 地址,无缝使用多种大模型能力,从而摆脱多套配置和接口不兼容的烦恼。项目支持模块化架构、策略模式和适配器模式,具备完整的测试覆盖和健康检查,开箱即用。
|
||||
|
||||
> [!NOTE]
|
||||
> 感谢阮一峰老师在[周刊359期](https://www.ruanyifeng.com/blog/2025/08/weekly-issue-359.html)的推荐。
|
||||
|
|
@ -34,15 +34,15 @@
|
|||
|
||||
## 💡 核心优势
|
||||
|
||||
* ✅ **多模型统一接入**:一个接口,通吃 Gemini、OpenAI、Claude、Kimi K2、GLM-4.5、Qwen Code 等多种最新模型。通过简单的启动参数或请求头,即可在不同模型服务商之间自由切换。
|
||||
* ✅ **突破官方限制**:通过支持 Gemini CLI 的 OAuth 授权方式,有效绕过官方免费 API 的速率和配额限制,让您享受更高的请求额度和使用频率。
|
||||
* ✅ **突破客户端限制**:Kiro API 模式下支持免费使用Claude Sonnet 4 模型。
|
||||
* ✅ **无缝兼容 OpenAI**:提供与 OpenAI API 完全兼容的接口,让您现有的工具链和客户端(如 LobeChat, NextChat 等)可以零成本接入所有支持的模型。
|
||||
* ✅ **账号池智能管理**:支持多账号轮询、故障转移和配置降级,确保服务高可用性,有效避免单一账号的限制问题。
|
||||
* ✅ **增强的可控性**:通过强大的日志功能,可以捕获并记录所有请求的提示词(Prompts),便于审计、调试和构建私有数据集。
|
||||
* ✅ **极易扩展**:得益于全新的模块化和策略模式设计,添加一个新的模型服务商变得前所未有的简单。
|
||||
* ✅ **完整测试覆盖**:提供全面的集成测试和单元测试,确保各个API端点和功能的稳定性和可靠性。
|
||||
* ✅ **Docker支持**:提供完整的Docker容器化支持,支持快速部署和环境隔离。
|
||||
* ✅ **多模型统一接入**:通过统一的 OpenAI 兼容接口,轻松接入 Gemini、OpenAI、Claude、Kimi K2、GLM-4.5、Qwen Code 等多种主流大模型,并通过启动参数或请求头自由切换。
|
||||
* ✅ **突破官方限制**:利用 Gemini CLI 的 OAuth 授权,有效规避官方免费 API 的速率和配额限制,提升请求额度和使用频率。
|
||||
* ✅ **免费使用 Claude Sonnet 4.5**:在 Kiro API 模式下,支持免费使用 Claude Sonnet 4.5 模型。
|
||||
* ✅ **无缝兼容 OpenAI**:提供与 OpenAI API 完全兼容的接口,使 LobeChat, NextChat 等现有工具链和客户端能零成本接入所有支持模型。
|
||||
* ✅ **账号池智能管理**:支持多账号轮询、故障转移和配置降级,确保服务高可用性,有效应对单一账号限制。
|
||||
* ✅ **增强的可控性**:强大的日志功能可捕获并记录所有请求的提示词(Prompts),便于审计、调试、优化模型行为和构建私有数据集。
|
||||
* ✅ **极易扩展**:采用模块化和策略模式设计,新增模型服务商变得前所未有的简单。
|
||||
* ✅ **完整测试覆盖**:全面的集成和单元测试,确保各个 API 端点和功能的稳定可靠。
|
||||
* ✅ **Docker支持**:提供完整的 Docker 容器化支持,实现快速部署和环境隔离。
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -63,10 +63,13 @@
|
|||
## 🎨 模型协议与提供商关系图
|
||||
|
||||
|
||||
- OpenAI 协议 (P_OPENAI): 支持所有 MODEL_PROVIDER,包括 openai-custom、gemini-cli-oauth、claude-custom、
|
||||
claude-kiro-oauth 和 openai-qwen-oauth。
|
||||
- Claude 协议 (P_CLAUDE): 支持 claude-custom、claude-kiro-oauth、gemini-cli-oauth、openai-custom和 openai-qwen-oauth。
|
||||
- Gemini 协议 (P_GEMINI): 支持 gemini-cli-oauth。
|
||||
本项目通过不同的协议(Protocol)支持多种模型提供商(Model Provider)。以下是它们之间的关系概述:
|
||||
|
||||
* **OpenAI 协议 (P_OPENAI)**:由 `openai-custom`, `gemini-cli-oauth`, `claude-custom`, `claude-kiro-oauth` 和 `openai-qwen-oauth` 等模型提供商实现。
|
||||
* **Claude 协议 (P_CLAUDE)**:由 `claude-custom`, `claude-kiro-oauth`, `gemini-cli-oauth`, `openai-custom` 和 `openai-qwen-oauth` 等模型提供商实现。
|
||||
* **Gemini 协议 (P_GEMINI)**:由 `gemini-cli-oauth` 模型提供商实现。
|
||||
|
||||
详细关系图如下:
|
||||
|
||||
|
||||
```mermaid
|
||||
|
|
@ -110,26 +113,33 @@ claude-kiro-oauth 和 openai-qwen-oauth。
|
|||
|
||||
## 🔧 使用说明
|
||||
|
||||
* **MCP 支持**: 虽然原版 Gemini CLI 的内置命令功能不可用,但本项目完美支持 MCP (Model Context Protocol),可配合支持 MCP 的客户端实现更强大的功能扩展。
|
||||
* **MCP 支持**:本项目完美支持 MCP (Model Context Protocol),可配合支持 MCP 的客户端实现功能扩展。
|
||||
* **多模态能力**: 支持图片、文档等多模态输入,为您提供更丰富的交互体验。
|
||||
* **最新模型支持**: 支持最新的 **Kimi K2**、**GLM-4.5** 和 **Qwen Code** 模型,只需在 `config.json` 中配置相应的 OpenAI 或 Claude 兼容接口即可使用。
|
||||
* **Qwen Code 支持**: 使用 Qwen Code 会自动在浏览器打开授权页面,完成授权后会在 `~/.qwen` 目录下生成 `oauth_creds.json` 文件。请使用官方默认参数 temperature=0 , top_p=1。
|
||||
* **Kiro API**: 使用 Kiro API 需要[下载kiro客户端](https://aibook.ren/archives/kiro-install)并完成授权登录生成 kiro-auth-token.json。**推荐配合 Claude Code 使用以获得最佳体验**。注意:Kiro服务政策已调整,具体使用限制请查看官方公告。
|
||||
* **Claude Code 中使用不同供应商**: 通过 Path 路由或环境变量,您可以在 Claude 相关的 API 调用中使用不同的供应商:
|
||||
* `http://localhost:3000/claude-custom` - 使用配置文件中定义的 Claude API 供应商
|
||||
* `http://localhost:3000/claude-kiro-oauth` - 使用 Kiro OAuth 认证方式访问 Claude API
|
||||
* `http://localhost:3000/openai-custom` - 使用 OpenAI 自定义供应商处理 Claude 请求
|
||||
* `http://localhost:3000/gemini-cli-oauth` - 使用 Gemini CLI OAuth 供应商处理 Claude 请求
|
||||
* `http://localhost:3000/openai-qwen-oauth` - 使用 Qwen OAuth 供应商处理 Claude 请求
|
||||
* 每个供应商可以配置不同的 API 密钥、基础 URL 和其他参数,实现灵活的供应商切换
|
||||
* **Qwen Code 支持**:
|
||||
* **授权流程**:首次使用 Qwen Code 时,会自动在浏览器中打开授权页面。完成授权后,`oauth_creds.json` 文件将生成并存储在 `~/.qwen` 目录下。
|
||||
* **模型参数**:请使用官方默认参数 `temperature=0` 和 `top_p=1`。
|
||||
* **Kiro API**:
|
||||
* **使用前提**:使用 Kiro API 需要[下载 Kiro 客户端](https://aibook.ren/archives/kiro-install)并完成授权登录,以生成 `kiro-auth-token.json` 文件。
|
||||
* **最佳体验**:推荐配合 Claude Code 使用以获得最佳体验。
|
||||
* **注意事项**:Kiro 服务政策已调整,请查阅官方公告了解具体使用限制。
|
||||
* **模型供应商切换**:本项目支持通过 Path 路由和环境变量两种方式,在 API 调用中灵活切换不同的模型供应商。
|
||||
|
||||
这些 Path 路由不仅可以在直接 API 调用中使用,也可以在 Cline、Kilo 等编程 agent 中使用,通过指定不同的路径来调用相应的模型。例如,在编程 agent 中配置 API 端点时,可以使用 `http://localhost:3000/claude-kiro-oauth` 来调用通过 Kiro OAuth 认证的 Claude 模型,或使用 `http://localhost:3000/gemini-cli-oauth` 来调用 Gemini 模型。
|
||||
#### 通过 Path 路由切换
|
||||
通过在 API 请求路径中包含特定的供应商标识,您可以直接调用对应的模型:
|
||||
* `http://localhost:3000/claude-custom` - 使用配置文件中定义的 Claude API 供应商。
|
||||
* `http://localhost:3000/claude-kiro-oauth` - 使用 Kiro OAuth 认证方式访问 Claude API。
|
||||
* `http://localhost:3000/openai-custom` - 使用 OpenAI 自定义供应商处理 Claude 请求。
|
||||
* `http://localhost:3000/gemini-cli-oauth` - 使用 Gemini CLI OAuth 供应商处理 Claude 请求。
|
||||
* `http://localhost:3000/openai-qwen-oauth` - 使用 Qwen OAuth 供应商处理 Claude 请求。
|
||||
|
||||
除了通过 Path 路由切换供应商外,您还可以通过设置环境变量来配置 Claude 参数。可以通过以下环境变量进行配置:
|
||||
这些 Path 路由不仅适用于直接 API 调用,也可在 Cline、Kilo 等编程 Agent 中配置 API 端点时使用,实现灵活的模型调用。例如,将 Agent 的 API 端点设置为 `http://localhost:3000/claude-kiro-oauth` 即可调用通过 Kiro OAuth 认证的 Claude 模型。
|
||||
|
||||
* `ANTHROPIC_BASE_URL`: 设置 Claude API 的基础 URL 路径
|
||||
* `ANTHROPIC_AUTH_TOKEN`: 设置 Claude 服务的认证密钥
|
||||
* `ANTHROPIC_MODEL`: 设置需要使用的 Claude 模型
|
||||
#### 通过环境变量配置 Claude 参数
|
||||
除了 Path 路由,您还可以通过设置以下环境变量来配置 Claude 相关的参数:
|
||||
* `ANTHROPIC_BASE_URL`: 设置 Claude API 的基础 URL 路径。
|
||||
* `ANTHROPIC_AUTH_TOKEN`: 设置 Claude 服务的认证密钥。
|
||||
* `ANTHROPIC_MODEL`: 设置需要使用的 Claude 模型。
|
||||
|
||||
#### 不同系统中的环境变量设置方法
|
||||
|
||||
|
|
@ -175,7 +185,7 @@ claude-kiro-oauth 和 openai-qwen-oauth。
|
|||
|
||||
### 不同终端环境下的 HTTP 代理设置命令
|
||||
|
||||
为了确保 `AIClient2API` 能够正常访问外部 AI 服务(如 Google、OpenAI、Claude、Kiro 等),您可能需要在您的终端环境中设置 HTTP 代理。以下是针对不同操作系统的代理设置命令:
|
||||
为确保 `AIClient2API` 正常访问外部 AI 服务(如 Google、OpenAI、Claude、Kiro 等),您需要在终端环境中设置 HTTP 代理。以下是针对不同操作系统的代理设置命令:
|
||||
|
||||
#### Linux / macOS
|
||||
```bash
|
||||
|
|
@ -207,27 +217,26 @@ $env:HTTP_PROXY="http://your_proxy_address:port"
|
|||
|
||||
## 🌟 特殊用法与进阶技巧
|
||||
|
||||
* **🔌 对接任意 OpenAI 客户端**: 这是本项目的基本功能。将任何支持 OpenAI 的应用(如 LobeChat, NextChat, VS Code 插件等)的 API 地址指向本服务 (`http://localhost:3000`),即可无缝使用所有已配置的模型。
|
||||
* **🔌 对接任意 OpenAI 客户端**:这是本项目的核心功能。将任何支持 OpenAI 兼容 API 的应用(如 LobeChat, NextChat, VS Code 插件等)的 API 地址配置为指向本项目服务 (`http://localhost:3000`),即可无缝使用所有已配置的大模型。
|
||||
|
||||
* **🔍 中心化请求监控与审计**: 在 `config.json` 中设置 `"PROMPT_LOG_MODE": "file"` 来捕获所有请求和响应,并保存到本地日志文件。这对于分析、调试和优化提示词,甚至构建私有数据集都至关重要。
|
||||
* **🔍 中心化请求监控与审计**:通过在 `config.json` 中设置 `"PROMPT_LOG_MODE": "file"`,可以捕获并记录所有发送给模型的请求(包括提示词)和接收到的响应,并保存到本地日志文件。这对于后续的审计、调试、提示词优化,以及构建私有数据集都具有重要价值。
|
||||
|
||||
* **💡 动态系统提示词**:
|
||||
* 通过在 `config.json` 中设置 `SYSTEM_PROMPT_FILE_PATH` 和 `SYSTEM_PROMPT_MODE`,您可以更灵活地控制系统提示词的行为。
|
||||
* **支持的模式**:
|
||||
* `override`: 完全忽略客户端的系统提示词,强制使用文件中的内容。
|
||||
* `append`: 在客户端系统提示词的末尾追加文件中的内容,实现规则的补充。
|
||||
* 这使得您可以为不同的客户端设置统一的基础指令,同时允许单个应用进行个性化扩展。
|
||||
* **💡 动态系统提示词**:通过配置 `config.json` 中的 `SYSTEM_PROMPT_FILE_PATH` 和 `SYSTEM_PROMPT_MODE` 参数,可以灵活控制系统提示词的行为,实现对模型预设指令的定制化管理。
|
||||
* **可用模式**:
|
||||
* `override`: 强制使用指定文件中的系统提示词,完全覆盖客户端发送的提示词。
|
||||
* `append`: 在客户端发送的系统提示词末尾追加指定文件中的内容,作为补充指令。
|
||||
* 此功能支持为不同客户端设置统一的基础指令,同时允许应用进行个性化扩展。
|
||||
|
||||
* **🛠️ 作为二次开发基石**:
|
||||
* **添加新模型**: 只需在 `src` 目录下创建一个新的提供商目录,实现 `ApiServiceAdapter` 接口和相应的策略,然后在 `adapter.js` 和 `common.js` 中注册即可。
|
||||
* **响应缓存**: 对高频重复问题添加缓存逻辑,降低 API 调用,提升响应速度。
|
||||
* **自定义内容过滤**: 在请求发送或返回前增加关键词过滤或内容审查逻辑,满足合规要求。
|
||||
* **🛠️ 作为二次开发基石**:
|
||||
* **添加新模型提供商**:在 `src` 目录下创建新的提供商目录,实现 `ApiServiceAdapter` 接口及相应策略,并在 `adapter.js` 和 `common.js` 中注册即可轻松集成新的大模型服务。
|
||||
* **响应缓存**:通过引入缓存机制,可针对高频重复请求进行响应缓存,有效降低 API 调用次数,显著提升响应速度。
|
||||
* **自定义内容过滤**:在请求发送前或接收响应后,可自定义添加关键词过滤或内容审查逻辑,以满足特定的合规性或安全要求。
|
||||
|
||||
* **🎯 账号池高级配置**:
|
||||
* **多账号管理**: 通过配置 `provider_pools.json` 文件,可以为每个提供商配置多个账号,实现智能轮询。
|
||||
* **故障转移**: 当某个账号失效时,系统会自动切换到下一个可用账号,确保服务连续性。
|
||||
* **配置降级**: 支持根据账号状态动态调整配置参数,优化资源使用效率。
|
||||
* **使用示例**: 参考项目中的 `provider_pools.json` 配置文件,轻松设置多账号环境。
|
||||
* **🎯 账号池高级配置**:
|
||||
* **多账号管理**:通过配置 `provider_pools.json` 文件,可为每个模型提供商设置多个账号,实现智能轮询,提高资源利用率。
|
||||
* **故障转移**:当检测到某个账号失效时,系统将自动切换至下一个可用账号,确保服务的持续稳定运行。
|
||||
* **配置降级**:支持根据账号的实时状态动态调整配置参数,例如在特定情况下自动切换到低消耗模型,以优化资源使用效率。
|
||||
* **使用示例**:请参考项目提供的 `provider_pools.json` 配置文件示例,以便轻松配置多账号环境。
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -241,7 +250,7 @@ $env:HTTP_PROXY="http://your_proxy_address:port"
|
|||
|------|------|--------|------|
|
||||
| `--host` | string | localhost | 服务器监听地址 |
|
||||
| `--port` | number | 3000 | 服务器监听端口 |
|
||||
| `--api-key` | string | 123456 | 身份验证所需的 API 密钥 |
|
||||
| `--api-key` | string | 123456 | 用于 API 身份验证的密钥 |
|
||||
|
||||
### 🤖 模型提供商配置参数
|
||||
|
||||
|
|
@ -253,36 +262,36 @@ $env:HTTP_PROXY="http://your_proxy_address:port"
|
|||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `--openai-api-key` | string | null | OpenAI API 密钥 (用于 openai-custom 提供商) |
|
||||
| `--openai-base-url` | string | null | OpenAI API 基础 URL (用于 openai-custom 提供商) |
|
||||
| `--openai-api-key` | string | null | OpenAI API 密钥 (当 `model-provider` 为 `openai-custom` 时必需) |
|
||||
| `--openai-base-url` | string | null | OpenAI API 基础 URL (当 `model-provider` 为 `openai-custom` 时必需) |
|
||||
|
||||
### 🖥️ Claude 兼容提供商参数
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `--claude-api-key` | string | null | Claude API 密钥 (用于 claude-custom 提供商) |
|
||||
| `--claude-base-url` | string | null | Claude API 基础 URL (用于 claude-custom 提供商) |
|
||||
| `--claude-api-key` | string | null | Claude API 密钥 (当 `model-provider` 为 `claude-custom` 时必需) |
|
||||
| `--claude-base-url` | string | null | Claude API 基础 URL (当 `model-provider` 为 `claude-custom` 时必需) |
|
||||
|
||||
### 🔐 Gemini OAuth 认证参数
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `--gemini-oauth-creds-base64` | string | null | Gemini OAuth 凭据的 Base64 字符串 |
|
||||
| `--gemini-oauth-creds-file` | string | null | Gemini OAuth 凭据 JSON 文件路径 |
|
||||
| `--project-id` | string | null | Google Cloud 项目 ID (用于 gemini-cli 提供商) |
|
||||
| `--gemini-oauth-creds-base64` | string | null | Gemini OAuth 凭据的 Base64 字符串 (当 `model-provider` 为 `gemini-cli-oauth` 时可选,与 `--gemini-oauth-creds-file` 二选一) |
|
||||
| `--gemini-oauth-creds-file` | string | null | Gemini OAuth 凭据 JSON 文件路径 (当 `model-provider` 为 `gemini-cli-oauth` 时可选,与 `--gemini-oauth-creds-base64` 二选一) |
|
||||
| `--project-id` | string | null | Google Cloud 项目 ID (当 `model-provider` 为 `gemini-cli-oauth` 时必需) |
|
||||
|
||||
### 🎮 Kiro OAuth 认证参数
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `--kiro-oauth-creds-base64` | string | null | Kiro OAuth 凭据的 Base64 字符串 |
|
||||
| `--kiro-oauth-creds-file` | string | null | Kiro OAuth 凭据 JSON 文件路径 |
|
||||
| `--kiro-oauth-creds-base64` | string | null | Kiro OAuth 凭据的 Base64 字符串 (当 `model-provider` 为 `claude-kiro-oauth` 时可选,与 `--kiro-oauth-creds-file` 二选一) |
|
||||
| `--kiro-oauth-creds-file` | string | null | Kiro OAuth 凭据 JSON 文件路径 (当 `model-provider` 为 `claude-kiro-oauth` 时可选,与 `--kiro-oauth-creds-base64` 二选一) |
|
||||
|
||||
### 🐼 Qwen OAuth 认证参数
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `--qwen-oauth-creds-file` | string | null | Qwen OAuth 凭据 JSON 文件路径 |
|
||||
| `--qwen-oauth-creds-file` | string | null | Qwen OAuth 凭据 JSON 文件路径 (当 `model-provider` 为 `openai-qwen-oauth` 时必需) |
|
||||
|
||||
### 📝 系统提示配置参数
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const KIRO_CONSTANTS = {
|
|||
REFRESH_IDC_URL: 'https://oidc.{{region}}.amazonaws.com/token',
|
||||
BASE_URL: 'https://codewhisperer.{{region}}.amazonaws.com/generateAssistantResponse',
|
||||
AMAZON_Q_URL: 'https://codewhisperer.{{region}}.amazonaws.com/SendMessageStreaming',
|
||||
DEFAULT_MODEL_NAME: 'kiro-claude-sonnet-4-20250514',
|
||||
DEFAULT_MODEL_NAME: 'claude-sonnet-4-20250514',
|
||||
AXIOS_TIMEOUT: 120000, // 2 minutes timeout
|
||||
USER_AGENT: 'KiroIDE',
|
||||
CONTENT_TYPE_JSON: 'application/json',
|
||||
|
|
@ -21,6 +21,8 @@ const KIRO_CONSTANTS = {
|
|||
};
|
||||
|
||||
const MODEL_MAPPING = {
|
||||
"claude-sonnet-4-5": "CLAUDE_SONNET_4_5_20250929_V1_0",
|
||||
"claude-sonnet-4-5-20250929": "CLAUDE_SONNET_4_5_20250929_V1_0",
|
||||
"claude-sonnet-4-20250514": "CLAUDE_SONNET_4_20250514_V1_0",
|
||||
"claude-3-7-sonnet-20250219": "CLAUDE_3_7_SONNET_20250219_V1_0",
|
||||
"amazonq-claude-sonnet-4-20250514": "CLAUDE_SONNET_4_20250514_V1_0",
|
||||
|
|
|
|||
|
|
@ -296,6 +296,16 @@ export async function handleUnaryRequest(res, service, model, requestBody, fromP
|
|||
uuid: pooluuid
|
||||
});
|
||||
}
|
||||
|
||||
// 返回错误响应给客户端
|
||||
const errorResponse = {
|
||||
error: {
|
||||
message: error.message || "An error occurred during processing.",
|
||||
code: error.status || 500,
|
||||
details: error.stack
|
||||
}
|
||||
};
|
||||
await handleUnifiedResponse(res, JSON.stringify(errorResponse), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { MODEL_PROTOCOL_PREFIX, getProtocolPrefix } from './common.js';
|
|||
const DEFAULT_MAX_TOKENS = 8192;
|
||||
const DEFAULT_GEMINI_MAX_TOKENS = 65536;
|
||||
const DEFAULT_TEMPERATURE = 1;
|
||||
const DEFAULT_TOP_P = 0.9;
|
||||
const DEFAULT_TOP_P = 0.95;
|
||||
|
||||
// 辅助函数:判断值是否为 undefined 或 0,并返回默认值
|
||||
function checkAndAssignOrDefault(value, defaultValue) {
|
||||
|
|
@ -252,7 +252,7 @@ export function convertData(data, type, fromProvider, toProvider, model) {
|
|||
export function toOpenAIRequestFromGemini(geminiRequest) {
|
||||
const openaiRequest = {
|
||||
messages: [],
|
||||
model: geminiRequest.model || "gpt-3.5-turbo", // Default model if not specified in Gemini request
|
||||
model: geminiRequest.model, // Default model if not specified in Gemini request
|
||||
max_tokens: checkAndAssignOrDefault(geminiRequest.max_tokens, DEFAULT_MAX_TOKENS),
|
||||
temperature: checkAndAssignOrDefault(geminiRequest.temperature, DEFAULT_TEMPERATURE),
|
||||
top_p: checkAndAssignOrDefault(geminiRequest.top_p, DEFAULT_TOP_P),
|
||||
|
|
@ -821,7 +821,7 @@ export function toOpenAIRequestFromClaude(claudeRequest) {
|
|||
}
|
||||
|
||||
const openaiRequest = {
|
||||
model: claudeRequest.model || 'gpt-3.5-turbo', // Default OpenAI model
|
||||
model: claudeRequest.model, // Default OpenAI model
|
||||
messages: openaiMessages,
|
||||
max_tokens: checkAndAssignOrDefault(claudeRequest.max_tokens, DEFAULT_MAX_TOKENS),
|
||||
temperature: checkAndAssignOrDefault(claudeRequest.temperature, DEFAULT_TEMPERATURE),
|
||||
|
|
@ -1512,7 +1512,7 @@ export function toClaudeRequestFromOpenAI(openaiRequest) {
|
|||
}
|
||||
|
||||
const claudeRequest = {
|
||||
model: openaiRequest.model || 'claude-3-opus-20240229',
|
||||
model: openaiRequest.model,
|
||||
messages: claudeMessages,
|
||||
max_tokens: checkAndAssignOrDefault(openaiRequest.max_tokens, DEFAULT_MAX_TOKENS),
|
||||
temperature: checkAndAssignOrDefault(openaiRequest.temperature, DEFAULT_TEMPERATURE),
|
||||
|
|
@ -1687,6 +1687,12 @@ export function toGeminiRequestFromClaude(claudeRequest) {
|
|||
return null; // Return null for invalid tools, filter out later
|
||||
}
|
||||
|
||||
// Filter out TodoWrite tool
|
||||
// if (tool.name === 'TodoWrite') {
|
||||
// console.log("Filtering out TodoWrite tool");
|
||||
// return null;
|
||||
// }
|
||||
|
||||
delete tool.input_schema.$schema;
|
||||
return {
|
||||
name: String(tool.name), // Ensure name is string
|
||||
|
|
@ -1786,6 +1792,11 @@ function processClaudeContentToGeminiParts(content) {
|
|||
|
||||
case 'tool_use':
|
||||
if (typeof block.name === 'string' && block.input && typeof block.input === 'object') {
|
||||
// Filter out TodoWrite tool use
|
||||
// if (block.name === 'TodoWrite') {
|
||||
// console.log("Filtering out TodoWrite tool use");
|
||||
// break; // Skip adding this tool to parts
|
||||
// }
|
||||
parts.push({
|
||||
functionCall: {
|
||||
name: block.name,
|
||||
|
|
@ -1907,9 +1918,25 @@ function processGeminiResponseToClaudeContent(geminiResponse) {
|
|||
|
||||
const content = [];
|
||||
|
||||
geminiResponse.candidates.forEach(candidate => {
|
||||
for (const candidate of geminiResponse.candidates) {
|
||||
// 检查完成原因是否为错误类型
|
||||
if (candidate.finishReason && candidate.finishReason !== 'STOP') {
|
||||
// console.log('Gemini response finishReason:', JSON.stringify(candidate));
|
||||
// console.warn('Gemini response contains malformed function call:', candidate.finishMessage || 'No finish message');
|
||||
|
||||
// 将错误信息作为文本内容返回
|
||||
if (candidate.finishMessage) {
|
||||
content.push({
|
||||
type: 'text',
|
||||
text: `Error: ${candidate.finishMessage}`
|
||||
});
|
||||
}
|
||||
// console.log("Processed content:", content);
|
||||
continue; // 跳过当前候选的进一步处理
|
||||
}
|
||||
|
||||
if (candidate.content && candidate.content.parts) {
|
||||
candidate.content.parts.forEach(part => {
|
||||
for (const part of candidate.content.parts) {
|
||||
if (part.text) {
|
||||
content.push({
|
||||
type: 'text',
|
||||
|
|
@ -1933,9 +1960,9 @@ function processGeminiResponseToClaudeContent(geminiResponse) {
|
|||
input: part.functionCall.args || {}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,22 @@ const CODE_ASSIST_API_VERSION = 'v1internal';
|
|||
const OAUTH_CLIENT_ID = '681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com';
|
||||
const OAUTH_CLIENT_SECRET = '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';
|
||||
const GEMINI_MODELS = ['gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemini-2.5-pro' , 'gemini-2.5-pro-preview-06-05'];
|
||||
const ANTI_TRUNCATION_MODELS = GEMINI_MODELS.map(model => `anti-${model}`);
|
||||
|
||||
function is_anti_truncation_model(model) {
|
||||
return ANTI_TRUNCATION_MODELS.some(antiModel => model.includes(antiModel) || antiModel.includes(model));
|
||||
}
|
||||
|
||||
// 从防截断模型名中提取实际模型名
|
||||
function extract_model_from_anti_model(model) {
|
||||
if (model.startsWith('anti-')) {
|
||||
const originalModel = model.substring(5); // 移除 'anti-' 前缀
|
||||
if (GEMINI_MODELS.includes(originalModel)) {
|
||||
return originalModel;
|
||||
}
|
||||
}
|
||||
return model; // 如果不是anti-前缀或不在原模型列表中,则返回原模型名
|
||||
}
|
||||
|
||||
function toGeminiApiResponse(codeAssistResponse) {
|
||||
if (!codeAssistResponse) return null;
|
||||
|
|
@ -25,6 +41,78 @@ function toGeminiApiResponse(codeAssistResponse) {
|
|||
return compliantResponse;
|
||||
}
|
||||
|
||||
async function* apply_anti_truncation_to_stream(service, model, requestBody) {
|
||||
let currentRequest = { ...requestBody };
|
||||
let allGeneratedText = '';
|
||||
|
||||
while (true) {
|
||||
// 发送请求并处理流式响应
|
||||
const apiRequest = {
|
||||
model: model,
|
||||
project: service.projectId,
|
||||
request: currentRequest
|
||||
};
|
||||
const stream = service.streamApi(API_ACTIONS.STREAM_GENERATE_CONTENT, apiRequest);
|
||||
|
||||
let lastChunk = null;
|
||||
let hasContent = false;
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const response = toGeminiApiResponse(chunk.response);
|
||||
if (response && response.candidates && response.candidates[0]) {
|
||||
yield response;
|
||||
lastChunk = response;
|
||||
hasContent = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否因为达到token限制而截断
|
||||
if (lastChunk &&
|
||||
lastChunk.candidates &&
|
||||
lastChunk.candidates[0] &&
|
||||
lastChunk.candidates[0].finishReason === 'MAX_TOKENS') {
|
||||
|
||||
// 提取已生成的文本内容
|
||||
if (lastChunk.candidates[0].content && lastChunk.candidates[0].content.parts) {
|
||||
const generatedParts = lastChunk.candidates[0].content.parts
|
||||
.filter(part => part.text)
|
||||
.map(part => part.text);
|
||||
|
||||
if (generatedParts.length > 0) {
|
||||
const currentGeneratedText = generatedParts.join('');
|
||||
allGeneratedText += currentGeneratedText;
|
||||
|
||||
// 构建新的请求,包含之前的对话历史和继续指令
|
||||
const newContents = [...requestBody.contents];
|
||||
|
||||
// 添加之前生成的内容作为模型响应
|
||||
newContents.push({
|
||||
role: 'model',
|
||||
parts: [{ text: currentGeneratedText }]
|
||||
});
|
||||
|
||||
// 添加继续生成的指令
|
||||
newContents.push({
|
||||
role: 'user',
|
||||
parts: [{ text: 'Please continue from where you left off.' }]
|
||||
});
|
||||
|
||||
currentRequest = {
|
||||
...requestBody,
|
||||
contents: newContents
|
||||
};
|
||||
|
||||
// 继续下一轮请求
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有截断或无法继续,则退出循环
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export class GeminiApiService {
|
||||
constructor(config) {
|
||||
this.authClient = new OAuth2Client(OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET);
|
||||
|
|
@ -326,16 +414,27 @@ export class GeminiApiService {
|
|||
|
||||
async * generateContentStream(model, requestBody) {
|
||||
console.log(`[Auth Token] Time until expiry: ${formatExpiryTime(this.authClient.credentials.expiry_date)}`);
|
||||
let selectedModel = model;
|
||||
if (!GEMINI_MODELS.includes(model)) {
|
||||
console.warn(`[Gemini] Model '${model}' not found. Using default model: '${GEMINI_MODELS[0]}'`);
|
||||
selectedModel = GEMINI_MODELS[0];
|
||||
}
|
||||
const processedRequestBody = ensureRolesInContents(requestBody);
|
||||
const apiRequest = { model: selectedModel, project: this.projectId, request: processedRequestBody };
|
||||
const stream = this.streamApi(API_ACTIONS.STREAM_GENERATE_CONTENT, apiRequest);
|
||||
for await (const chunk of stream) {
|
||||
yield toGeminiApiResponse(chunk.response);
|
||||
|
||||
// 检查是否为防截断模型
|
||||
if (is_anti_truncation_model(model)) {
|
||||
// 从防截断模型名中提取实际模型名
|
||||
const actualModel = extract_model_from_anti_model(model);
|
||||
// 使用防截断流处理
|
||||
const processedRequestBody = ensureRolesInContents(requestBody);
|
||||
yield* apply_anti_truncation_to_stream(this, actualModel, processedRequestBody);
|
||||
} else {
|
||||
// 正常流处理
|
||||
let selectedModel = model;
|
||||
if (!GEMINI_MODELS.includes(model)) {
|
||||
console.warn(`[Gemini] Model '${model}' not found. Using default model: '${GEMINI_MODELS[0]}'`);
|
||||
selectedModel = GEMINI_MODELS[0];
|
||||
}
|
||||
const processedRequestBody = ensureRolesInContents(requestBody);
|
||||
const apiRequest = { model: selectedModel, project: this.projectId, request: processedRequestBody };
|
||||
const stream = this.streamApi(API_ACTIONS.STREAM_GENERATE_CONTENT, apiRequest);
|
||||
for await (const chunk of stream) {
|
||||
yield toGeminiApiResponse(chunk.response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue