diff --git a/.gitignore b/.gitignore index 7072e69..f752bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,4 @@ api-potluck-keys.json api-potluck-data.json # Codex credentials configs/codex/ -# Orchids credentials -configs/orchids/*_orchids_creds/ -configs/orchids/*.json -!configs/orchids/*.example + diff --git a/README-JA.md b/README-JA.md index a1e7de0..5ee7819 100644 --- a/README-JA.md +++ b/README-JA.md @@ -34,7 +34,6 @@ >
> クリックして詳細なバージョン履歴を展開 > -> - **2026.01.22** - Orchidsプロトコルサポートの追加、Clerk OAuth認証でClaude Sonnet 4.5、Claude Opus 4.5、Gemini 3 Flash、GPT-5.2などのモデルにアクセス可能、WebSocketストリーミングとツール呼び出しをサポート > - **2026.01.15** - プロバイダープールマネージャーの最適化:非同期リフレッシュキューメカニズム、バッファキュー重複排除、グローバル並行制御、ノードウォームアップと自動期限切れ検出を追加 > - **2026.01.07** - iFlowプロトコルサポートの追加、OAuth認証方式でQwen、Kimi、DeepSeek、GLMシリーズモデルにアクセス可能、自動トークンリフレッシュ機能をサポート > - **2026.01.03** - テーマ切替機能を追加し、プロバイダープール初期化を最適化、プロバイダーのデフォルト設定を使用するフォールバック戦略を削除 @@ -249,13 +248,6 @@ Web UI管理インターフェースでは、極めて迅速に認証設定を 3. **ベストプラクティス**:**Claude Code**との併用を推奨、最適な体験を得られる 4. **重要なお知らせ**:Kiroサービス使用ポリシーが更新されました、最新の使用制限と条件については公式ウェブサイトをご確認ください。 -#### Orchids OAuth設定 -1. **プラットフォームにログイン**:[Orchidsプラットフォーム](https://orchids.app)にアクセスしてアカウントにログイン -2. **認証情報を取得**:ブラウザの開発者ツール(F12)を開き、Application > Cookies > https://orchids.app に移動 -3. **トークンをコピー**:`__client` を見つけてその値(長いJWT文字列)をコピー -4. **認証情報をインポート**:Web UIの「トークンをインポート」機能を使用して値を貼り付け -5. **サポートモデル**:Claude Sonnet 4.5、Claude Opus 4.5、Gemini 3 Flash、GPT-5.2など - #### iFlow OAuth設定 1. **初回認証**:Web UIの「設定管理」または「プロバイダープール」ページで、iFlowの「認証生成」ボタンをクリック 2. **電話番号ログイン**:システムがiFlow認証ページを開き、電話番号でログイン認証を完了 @@ -284,7 +276,6 @@ Web UI管理インターフェースでは、極めて迅速に認証設定を | **Kiro** | `~/.aws/sso/cache/kiro-auth-token.json` | Kiro認証トークン | | **Qwen** | `~/.qwen/oauth_creds.json` | Qwen OAuth認証情報 | | **Antigravity** | `~/.antigravity/oauth_creds.json` | Antigravity OAuth認証情報 (Claude 4.5 Opus サポート) | -| **Orchids** | `configs/orchids/{timestamp}_orchids_creds.json` | Orchids Clerk JWT認証情報 (Claude 4.5、GPT-5.2 サポート) | | **iFlow** | `~/.iflow/oauth_creds.json` | iFlow OAuth認証情報 (Qwen、Kimi、DeepSeek、GLM サポート) | > **説明**:`~`はユーザーホームディレクトリを表します(Windows: `C:\Users\ユーザー名`、Linux/macOS: `/home/ユーザー名`または`/Users/ユーザー名`) diff --git a/README-ZH.md b/README-ZH.md index 2491b4d..1f323aa 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -34,7 +34,6 @@ >
> 点击展开查看详细版本历史 > -> - **2026.01.22** - 新增 Orchids 协议支持,通过 Clerk OAuth 认证访问 Claude Sonnet 4.5、Claude Opus 4.5、Gemini 3 Flash、GPT-5.2 等模型,支持 WebSocket 流式传输和工具调用 > - **2026.01.15** - 优化提供商池管理器:新增异步刷新队列机制、缓冲队列去重、全局并发控制,支持节点预热和自动过期检测 > - **2026.01.07** - 新增 iFlow 协议支持,通过 OAuth 认证方式访问 Qwen、Kimi、DeepSeek 和 GLM 系列模型,支持自动 token 刷新功能 > - **2026.01.03** - 新增主题切换功能并优化提供商池初始化,移除使用提供商默认配置的降级策略 @@ -248,13 +247,6 @@ docker compose up -d 3. **最佳实践**:推荐配合 **Claude Code** 使用,可获得最优体验 4. **重要提示**:Kiro 服务使用政策已更新,请访问官方网站查看最新使用限制和条款 -#### Orchids OAuth 配置 -1. **登录平台**:访问 [Orchids 平台](https://orchids.app) 并登录账号 -2. **获取凭据**:打开浏览器开发者工具 (F12),切换到 Application > Cookies > https://orchids.app -3. **复制 Token**:找到 `__client` 并复制其值(一个长的 JWT 字符串) -4. **导入凭据**:在 Web UI 中使用"导入 Token"功能粘贴该值 -5. **支持模型**:Claude Sonnet 4.5、Claude Opus 4.5、Gemini 3 Flash、GPT-5.2 等 - #### iFlow OAuth 配置 1. **首次授权**:在 Web UI 的"配置管理"或"提供商池"页面,点击 iFlow 的"生成授权"按钮 2. **手机登录**:系统将打开 iFlow 授权页面,使用手机号完成登录验证 @@ -283,7 +275,6 @@ docker compose up -d | **Kiro** | `~/.aws/sso/cache/kiro-auth-token.json` | Kiro 认证令牌 | | **Qwen** | `~/.qwen/oauth_creds.json` | Qwen OAuth 凭据 | | **Antigravity** | `~/.antigravity/oauth_creds.json` | Antigravity OAuth 凭据 (支持 Claude 4.5 Opus) | -| **Orchids** | `configs/orchids/{timestamp}_orchids_creds.json` | Orchids Clerk JWT 凭据 (支持 Claude 4.5、GPT-5.2) | | **iFlow** | `~/.iflow/oauth_creds.json` | iFlow OAuth 凭据 (支持 Qwen、Kimi、DeepSeek、GLM) | > **说明**:`~` 表示用户主目录(Windows: `C:\Users\用户名`,Linux/macOS: `/home/用户名` 或 `/Users/用户名`) diff --git a/README.md b/README.md index ae2a838..5132cbe 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ >
> Click to expand detailed version history > -> - **2026.01.22** - Added Orchids protocol support, accessing Claude Sonnet 4.5, Claude Opus 4.5, Gemini 3 Flash, GPT-5.2 and more via Clerk OAuth authentication, with WebSocket streaming and tool calling support > - **2026.01.15** - Optimized provider pool manager: added async refresh queue mechanism, buffer queue deduplication, global concurrency control, node warmup and automatic expiry detection > - **2026.01.07** - Added iFlow protocol support, enabling access to Qwen, Kimi, DeepSeek, and GLM series models via OAuth authentication with automatic token refresh > - **2026.01.03** - Added theme switching functionality and optimized provider pool initialization, removed the fallback strategy of using provider default configuration @@ -249,13 +248,6 @@ In the Web UI management interface, you can complete authorization configuration 3. **Best Practice**: Recommended to use with **Claude Code** for optimal experience 4. **Important Notice**: Kiro service usage policy has been updated, please visit the official website for the latest usage restrictions and terms -#### Orchids OAuth Configuration -1. **Login to Platform**: Visit [Orchids Platform](https://orchids.app) and log in to your account -2. **Get Credentials**: Open browser developer tools (F12), navigate to Application > Cookies > https://orchids.app -3. **Copy Token**: Find `__client` and copy its value (a long JWT string) -4. **Import Credentials**: Use the "Import Token" function in Web UI to paste the value -5. **Supported Models**: Claude Sonnet 4.5, Claude Opus 4.5, Gemini 3 Flash, GPT-5.2, etc. - #### iFlow OAuth Configuration 1. **First Authorization**: In Web UI's "Configuration" or "Provider Pools" page, click the "Generate Authorization" button for iFlow 2. **Phone Login**: The system will open the iFlow authorization page, complete login verification using your phone number @@ -284,7 +276,6 @@ Default storage locations for authorization credential files of each service: | **Kiro** | `~/.aws/sso/cache/kiro-auth-token.json` | Kiro authentication token | | **Qwen** | `~/.qwen/oauth_creds.json` | Qwen OAuth credentials | | **Antigravity** | `~/.antigravity/oauth_creds.json` | Antigravity OAuth credentials (supports Claude 4.5 Opus) | -| **Orchids** | `configs/orchids/{timestamp}_orchids_creds.json` | Orchids Clerk JWT credentials (supports Claude 4.5, GPT-5.2) | | **iFlow** | `~/.iflow/oauth_creds.json` | iFlow OAuth credentials (supports Qwen, Kimi, DeepSeek, GLM) | > **Note**: `~` represents the user home directory (Windows: `C:\Users\username`, Linux/macOS: `/home/username` or `/Users/username`) diff --git a/configs/provider_pools.json.example b/configs/provider_pools.json.example index f1230ab..98e5643 100644 --- a/configs/provider_pools.json.example +++ b/configs/provider_pools.json.example @@ -135,21 +135,6 @@ "lastErrorTime": null } ], - "claude-orchids-oauth": [ - { - "customName": "Orchids节点1", - "ORCHIDS_CREDS_FILE_PATH": "./configs/orchids/orchids_creds.json", - "uuid": "xxx-xxx-xxx", - "checkModelName": "claude-sonnet-4-5", - "checkHealth": false, - "isHealthy": true, - "isDisabled": false, - "lastUsed": null, - "usageCount": 0, - "errorCount": 0, - "lastErrorTime": null - } - ], "openai-qwen-oauth": [ { "customName": "Qwen OAuth节点", diff --git a/src/auth/index.js b/src/auth/index.js index 29ec6a9..17f43cf 100644 --- a/src/auth/index.js +++ b/src/auth/index.js @@ -30,9 +30,3 @@ export { handleIFlowOAuth, refreshIFlowTokens } from './iflow-oauth.js'; - -// Orchids OAuth -export { - importOrchidsToken, - handleOrchidsOAuth -} from './orchids-oauth.js'; \ No newline at end of file diff --git a/src/auth/oauth-handlers.js b/src/auth/oauth-handlers.js index 712afeb..4ffef6b 100644 --- a/src/auth/oauth-handlers.js +++ b/src/auth/oauth-handlers.js @@ -21,7 +21,4 @@ export { // iFlow OAuth handleIFlowOAuth, refreshIFlowTokens, - // Orchids OAuth - importOrchidsToken, - handleOrchidsOAuth } from './index.js'; \ No newline at end of file diff --git a/src/auth/orchids-oauth.js b/src/auth/orchids-oauth.js deleted file mode 100644 index 8eb2c69..0000000 --- a/src/auth/orchids-oauth.js +++ /dev/null @@ -1,280 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { broadcastEvent } from '../services/ui-manager.js'; -import { autoLinkProviderConfigs } from '../services/service-manager.js'; -import { CONFIG } from '../core/config-manager.js'; - -/** - * Orchids OAuth 配置 - */ -const ORCHIDS_OAUTH_CONFIG = { - // Clerk Token 端点 - clerkTokenEndpoint: 'https://clerk.orchids.app/v1/client/sessions/{sessionId}/tokens', - clerkJsVersion: '5.114.0', - - // 凭据存储 - credentialsDir: 'orchids', - credentialsFile: 'orchids_creds.json', - - // 日志前缀 - logPrefix: '[Orchids Auth]' -}; - -/** - * 解析 Orchids 凭据字符串(简化版) - * 只需要 __client JWT 即可,其他参数通过 Clerk API 自动获取 - * - * 支持的格式: - * 1. 纯 JWT 字符串: "eyJhbGciOiJSUzI1NiJ9..." (从 payload 中提取 rotating_token) - * 2. __client=xxx 格式: "__client=eyJhbGciOiJSUzI1NiJ9..." - * 3. 完整 Cookies 格式(兼容旧版): "__client=xxx; __session=xxx" - * 4. JWT|xxx 格式(兼容旧版) - * - * @param {string} inputString - 输入字符串 - * @returns {Object} 解析后的凭据数据 - */ -function parseOrchidsCredentials(inputString) { - if (!inputString || typeof inputString !== 'string') { - throw new Error('Invalid input string'); - } - - const trimmedInput = inputString.trim(); - - // 格式1: 纯 JWT 字符串(三段式,以点分隔) - if (trimmedInput.split('.').length === 3 && !trimmedInput.includes('=') && !trimmedInput.includes('|')) { - console.log('[Orchids Auth] Detected pure JWT format'); - - // 尝试从 JWT payload 中提取 rotating_token - let rotatingToken = null; - try { - const parts = trimmedInput.split('.'); - if (parts.length === 3) { - // 解码 JWT payload (Base64URL -> Base64 -> JSON) - let payloadBase64 = parts[1].replace(/-/g, '+').replace(/_/g, '/'); - // 添加 padding - while (payloadBase64.length % 4) { - payloadBase64 += '='; - } - const payloadJson = Buffer.from(payloadBase64, 'base64').toString('utf8'); - const payload = JSON.parse(payloadJson); - - if (payload.rotating_token) { - rotatingToken = payload.rotating_token; - console.log('[Orchids Auth] Extracted rotating_token from JWT payload'); - } - } - } catch (e) { - console.warn('[Orchids Auth] Failed to extract rotating_token from JWT payload:', e.message); - } - - return { - type: 'jwt', - clientJwt: trimmedInput, - rotatingToken: rotatingToken - }; - } - - // 格式2: __client=xxx 格式(可能包含或不包含 __session) - if (trimmedInput.includes('__client=')) { - const clientMatch = trimmedInput.match(/__client=([^;]+)/); - if (clientMatch) { - const clientValue = clientMatch[1].trim(); - // 处理可能的 | 分隔符(如 JWT|rotating_token) - let jwtPart = clientValue; - let rotatingToken = null; - if (clientValue.includes('|')) { - const parts = clientValue.split('|'); - jwtPart = parts[0]; - rotatingToken = parts[1] || null; - } - - if (jwtPart.split('.').length === 3) { - console.log('[Orchids Auth] Detected __client cookie format'); - return { - type: 'jwt', - clientJwt: jwtPart, - rotatingToken: rotatingToken - }; - } - } - throw new Error('Invalid __client value. Expected a valid JWT.'); - } - - // 格式3: JWT|rotating_token 格式 - if (trimmedInput.includes('|')) { - const parts = trimmedInput.split('|'); - if (parts.length >= 1) { - const jwtPart = parts[0].trim(); - const rotatingToken = parts.length >= 2 ? parts[1].trim() : null; - if (jwtPart.split('.').length === 3) { - console.log('[Orchids Auth] Detected JWT|rotating_token format'); - return { - type: 'jwt', - clientJwt: jwtPart, - rotatingToken: rotatingToken - }; - } - } - } - - throw new Error('Invalid format. Please provide the __client cookie value (JWT format). Example: eyJhbGciOiJSUzI1NiJ9...'); -} - -/** - * 解析 Orchids JWT Token 字符串 (保留用于向后兼容) - * @deprecated 请使用 parseOrchidsCredentials - * 格式: JWT|rotating_token - * JWT 包含 id (client_id) 和 rotating_token - * @param {string} tokenString - 完整的 token 字符串 - * @returns {Object} 解析后的 token 数据 - */ -function parseOrchidsToken(tokenString) { - const result = parseOrchidsCredentials(tokenString); - if (result.type === 'legacy') { - return { - clientId: result.clientId, - rotatingToken: result.rotatingToken, - jwt: result.jwt, - rawPayload: result.rawPayload - }; - } - // 对于新格式,返回兼容的结构 - return { - clientId: null, - rotatingToken: result.clientValue, - jwt: null, - rawPayload: null - }; -} - -/** - * 从 Clerk 获取 session token - * @param {string} sessionId - Clerk session ID - * @param {string} cookies - Cookie 字符串 - * @returns {Promise} JWT token - */ -async function getClerkSessionToken(sessionId, cookies) { - const tokenUrl = ORCHIDS_OAUTH_CONFIG.clerkTokenEndpoint - .replace('{sessionId}', sessionId) + - `?_clerk_js_version=${ORCHIDS_OAUTH_CONFIG.clerkJsVersion}`; - - const response = await fetch(tokenUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Cookie': cookies, - 'Origin': 'https://www.orchids.app' - } - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Clerk token request failed: ${response.status} ${errorText}`); - } - - const data = await response.json(); - return data.jwt; -} - -/** - * 导入 Orchids 凭据并生成凭据文件(简化版) - * 只需要 __client JWT,其他参数在运行时通过 Clerk API 自动获取 - * - * @param {string} inputString - __client JWT 字符串 - * @param {Object} options - 额外选项 - * - workingDir: 默认工作目录 - * @returns {Promise} 导入结果 - */ -export async function importOrchidsToken(inputString, options = {}) { - try { - console.log(`${ORCHIDS_OAUTH_CONFIG.logPrefix} Parsing Orchids credentials (simplified)...`); - - // 解析凭据 - 只提取 clientJwt - const credData = parseOrchidsCredentials(inputString); - - if (!credData.clientJwt) { - throw new Error('Failed to extract clientJwt from input'); - } - - // 凭据数据 - 保存 clientJwt 和可选的 rotatingToken - const credentialsData = { - // 核心字段:__client JWT(必需的凭据) - clientJwt: credData.clientJwt, - // 导入时间 - importedAt: new Date().toISOString() - }; - - // 如果存在 rotatingToken,也保存它(可选,备用) - if (credData.rotatingToken) { - credentialsData.rotatingToken = credData.rotatingToken; - console.log(`${ORCHIDS_OAUTH_CONFIG.logPrefix} rotatingToken also saved for future use.`); - } - - // 生成文件路径: configs/orchids/{timestamp}_orchids_creds/{timestamp}_orchids_creds.json - const timestamp = Date.now(); - const folderName = `${timestamp}_orchids_creds`; - const targetDir = path.join(process.cwd(), 'configs', ORCHIDS_OAUTH_CONFIG.credentialsDir, folderName); - await fs.promises.mkdir(targetDir, { recursive: true }); - - const filename = `${folderName}.json`; - const credPath = path.join(targetDir, filename); - await fs.promises.writeFile(credPath, JSON.stringify(credentialsData, null, 2)); - - const relativePath = path.relative(process.cwd(), credPath); - - console.log(`${ORCHIDS_OAUTH_CONFIG.logPrefix} Credentials saved to: ${relativePath}`); - console.log(`${ORCHIDS_OAUTH_CONFIG.logPrefix} Only clientJwt is stored. Session info will be fetched at runtime.`); - - // 广播事件 - broadcastEvent('oauth_success', { - provider: 'claude-orchids-oauth', - relativePath: relativePath, - timestamp: new Date().toISOString() - }); - - // 自动关联新生成的凭据到 Pools - await autoLinkProviderConfigs(CONFIG); - - return { - success: true, - path: relativePath, - message: 'Credentials imported successfully. Session info will be fetched at runtime via Clerk API.' - }; - - } catch (error) { - console.error(`${ORCHIDS_OAUTH_CONFIG.logPrefix} Token import failed:`, error); - return { - success: false, - error: error.message - }; - } -} - -/** - * 处理 Orchids OAuth(手动导入模式 - 简化版) - * 只需要 __client JWT,其他参数自动获取 - * @param {Object} currentConfig - 当前配置对象 - * @param {Object} options - 额外选项 - * @returns {Promise} 返回导入说明 - */ -export async function handleOrchidsOAuth(currentConfig, options = {}) { - // Orchids 使用简化的手动导入模式 - // 只需要 __client cookie 的值 - return { - authUrl: null, - authInfo: { - provider: 'claude-orchids-oauth', - method: 'manual-import', - instructions: [ - '1. 登录 Orchids 平台 (https://orchids.app)', - '2. 打开浏览器开发者工具 (F12)', - '3. 切换到 Application > Cookies > https://orchids.app', - '4. 找到 __client 并复制其值(一个长的 JWT 字符串)', - '5. 使用 "导入 Token" 功能粘贴该值' - ], - tokenFormat: 'eyJhbGciOiJSUzI1NiJ9...', - example: 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNsaWVudF8uLi4', - note: '只需要 __client 的值即可,sessionId 等参数会自动获取' - } - }; -} \ No newline at end of file diff --git a/src/converters/strategies/GeminiConverter.js b/src/converters/strategies/GeminiConverter.js index 2ccd8ff..c1e7b53 100644 --- a/src/converters/strategies/GeminiConverter.js +++ b/src/converters/strategies/GeminiConverter.js @@ -585,11 +585,14 @@ export class GeminiConverter extends BaseConverter { } const candidate = geminiResponse.candidates[0]; - const content = this.processGeminiResponseToClaudeContent(geminiResponse); + const { content, hasToolUse } = this.processGeminiResponseToClaudeContent(geminiResponse); const finishReason = candidate.finishReason; let stopReason = "end_turn"; - if (finishReason) { + // [FIX] 参考 ag/response.rs - 如果有工具调用,stop_reason 应该是 "tool_use" + if (hasToolUse) { + stopReason = 'tool_use'; + } else if (finishReason) { switch (finishReason) { case 'STOP': stopReason = 'end_turn'; @@ -644,6 +647,7 @@ export class GeminiConverter extends BaseConverter { // [FIX] 参考 ag/streaming.rs 处理 thinking 和 text 块 if (parts && Array.isArray(parts)) { const results = []; + let hasToolUse = false; for (const part of parts) { if (!part) continue; @@ -662,8 +666,10 @@ export class GeminiConverter extends BaseConverter { results.push(thinkingResult); // 如果有签名,发送 signature_delta - if (part.thoughtSignature) { - let signature = part.thoughtSignature; + // [FIX] 同时检查 thoughtSignature 和 thought_signature + const rawSignature = part.thoughtSignature || part.thought_signature; + if (rawSignature) { + let signature = rawSignature; try { const decoded = Buffer.from(signature, 'base64').toString('utf-8'); if (decoded && decoded.length > 0 && !decoded.includes('\ufffd')) { @@ -696,6 +702,7 @@ export class GeminiConverter extends BaseConverter { // [FIX] 处理 functionCall if (part.functionCall) { + hasToolUse = true; // [FIX] 规范化工具名称和参数映射 const toolName = normalizeToolName(part.functionCall.name); const remappedArgs = remapFunctionCallArgs(toolName, part.functionCall.args || {}); @@ -724,6 +731,25 @@ export class GeminiConverter extends BaseConverter { } } + // [FIX] 如果有工具调用,添加 message_delta 事件设置 stop_reason 为 tool_use + if (hasToolUse && candidate.finishReason) { + const messageDelta = { + type: "message_delta", + delta: { + stop_reason: 'tool_use' + } + }; + if (geminiChunk.usageMetadata) { + messageDelta.usage = { + input_tokens: geminiChunk.usageMetadata.promptTokenCount || 0, + cache_creation_input_tokens: 0, + cache_read_input_tokens: geminiChunk.usageMetadata.cachedContentTokenCount || 0, + output_tokens: geminiChunk.usageMetadata.candidatesTokenCount || 0 + }; + } + results.push(messageDelta); + } + // 如果有多个结果,返回数组;否则返回单个或 null if (results.length > 1) { return results; @@ -802,6 +828,7 @@ export class GeminiConverter extends BaseConverter { // [FIX] 参考 ag/response.rs 处理 thinking 块 // Gemini 使用 thought: true 和 thoughtSignature 表示思考内容 + // [FIX] 同时支持 thoughtSignature 和 thought_signature(Gemini CLI 可能使用下划线格式) if (part.text) { if (part.thought === true) { // 这是一个 thinking 块 @@ -810,8 +837,10 @@ export class GeminiConverter extends BaseConverter { thinking: part.text }; // 处理签名 - 可能是 Base64 编码的 - if (part.thoughtSignature) { - let signature = part.thoughtSignature; + // [FIX] 同时检查 thoughtSignature 和 thought_signature + const rawSignature = part.thoughtSignature || part.thought_signature; + if (rawSignature) { + let signature = rawSignature; // 尝试 Base64 解码 try { const decoded = Buffer.from(signature, 'base64').toString('utf-8'); @@ -858,8 +887,10 @@ export class GeminiConverter extends BaseConverter { input: remappedArgs }; // [FIX] 如果有签名,添加到 tool_use 块 - if (part.thoughtSignature) { - let signature = part.thoughtSignature; + // [FIX] 同时检查 thoughtSignature 和 thought_signature + const rawSignature = part.thoughtSignature || part.thought_signature; + if (rawSignature) { + let signature = rawSignature; try { const decoded = Buffer.from(signature, 'base64').toString('utf-8'); if (decoded && decoded.length > 0 && !decoded.includes('\ufffd')) { @@ -893,9 +924,12 @@ export class GeminiConverter extends BaseConverter { /** * 处理Gemini响应到Claude内容 + * @returns {{ content: Array, hasToolUse: boolean }} */ processGeminiResponseToClaudeContent(geminiResponse) { - if (!geminiResponse || !geminiResponse.candidates || geminiResponse.candidates.length === 0) return []; + if (!geminiResponse || !geminiResponse.candidates || geminiResponse.candidates.length === 0) { + return { content: [], hasToolUse: false }; + } const content = []; let hasToolUse = false; @@ -922,8 +956,10 @@ export class GeminiConverter extends BaseConverter { thinking: part.text }; // 处理签名 - if (part.thoughtSignature) { - let signature = part.thoughtSignature; + // [FIX] 同时检查 thoughtSignature 和 thought_signature + const rawSignature = part.thoughtSignature || part.thought_signature; + if (rawSignature) { + let signature = rawSignature; try { const decoded = Buffer.from(signature, 'base64').toString('utf-8'); if (decoded && decoded.length > 0 && !decoded.includes('\ufffd')) { @@ -965,8 +1001,10 @@ export class GeminiConverter extends BaseConverter { input: remappedArgs }; // 添加签名(如果存在) - if (part.thoughtSignature) { - let signature = part.thoughtSignature; + // [FIX] 同时检查 thoughtSignature 和 thought_signature + const rawSignature = part.thoughtSignature || part.thought_signature; + if (rawSignature) { + let signature = rawSignature; try { const decoded = Buffer.from(signature, 'base64').toString('utf-8'); if (decoded && decoded.length > 0 && !decoded.includes('\ufffd')) { @@ -983,7 +1021,7 @@ export class GeminiConverter extends BaseConverter { } } - return content; + return { content, hasToolUse }; } // ========================================================================= diff --git a/src/handlers/ollama-handler.js b/src/handlers/ollama-handler.js index b3f5faa..f154a51 100644 --- a/src/handlers/ollama-handler.js +++ b/src/handlers/ollama-handler.js @@ -22,7 +22,6 @@ export const MODEL_PREFIX_MAP = { [MODEL_PROVIDER.QWEN_API]: '[Qwen CLI]', [MODEL_PROVIDER.OPENAI_CUSTOM_RESPONSES]: '[OpenAI Responses]', [MODEL_PROVIDER.ANTIGRAVITY]: '[Antigravity]', - [MODEL_PROVIDER.ORCHIDS_API]: '[Orchids]', [MODEL_PROVIDER.IFLOW_API]: '[iFlow]', } @@ -249,7 +248,6 @@ function findProviderByModelName(modelName) { MODEL_PROVIDER.GEMINI_CLI, MODEL_PROVIDER.ANTIGRAVITY, MODEL_PROVIDER.KIRO_API, - MODEL_PROVIDER.ORCHIDS_API, MODEL_PROVIDER.QWEN_API, MODEL_PROVIDER.IFLOW_API ]; diff --git a/src/providers/adapter.js b/src/providers/adapter.js index 649fd04..2a0d7a2 100644 --- a/src/providers/adapter.js +++ b/src/providers/adapter.js @@ -4,7 +4,6 @@ import { AntigravityApiService } from './gemini/antigravity-core.js'; // 导入A import { OpenAIApiService } from './openai/openai-core.js'; // 导入OpenAIApiService import { ClaudeApiService } from './claude/claude-core.js'; // 导入ClaudeApiService import { KiroApiService } from './claude/claude-kiro.js'; // 导入KiroApiService -import { OrchidsApiService } from './claude/claude-orchids.js'; // 导入OrchidsApiService import { QwenApiService } from './openai/qwen-core.js'; // 导入QwenApiService import { IFlowApiService } from './openai/iflow-core.js'; // 导入IFlowApiService import { CodexApiService } from './openai/codex-core.js'; // 导入CodexApiService @@ -407,65 +406,6 @@ export class KiroApiServiceAdapter extends ApiServiceAdapter { } } -// Orchids API 服务适配器 -export class OrchidsApiServiceAdapter extends ApiServiceAdapter { - constructor(config) { - super(); - this.orchidsApiService = new OrchidsApiService(config); - } - - async generateContent(model, requestBody) { - if (!this.orchidsApiService.isInitialized) { - await this.orchidsApiService.initialize(); - } - return this.orchidsApiService.generateContent(model, requestBody); - } - - async *generateContentStream(model, requestBody) { - if (!this.orchidsApiService.isInitialized) { - await this.orchidsApiService.initialize(); - } - yield* this.orchidsApiService.generateContentStream(model, requestBody); - } - - async listModels() { - return this.orchidsApiService.listModels(); - } - - async refreshToken() { - if (!this.orchidsApiService.isInitialized) { - await this.orchidsApiService.initialize(); - } - if (this.isExpiryDateNear()) { - return this.orchidsApiService.initializeAuth(true); - } - return Promise.resolve(); - } - - async forceRefreshToken() { - if (!this.orchidsApiService.isInitialized) { - await this.orchidsApiService.initialize(); - } - console.log(`[Orchids] Force refreshing token...`); - return this.orchidsApiService.initializeAuth(true); - } - - isExpiryDateNear() { - return this.orchidsApiService.isExpiryDateNear(); - } - - async getUsageLimits() { - if (!this.orchidsApiService.isInitialized) { - await this.orchidsApiService.initialize(); - } - return this.orchidsApiService.getUsageLimits(); - } - - countTokens(requestBody) { - return this.orchidsApiService.countTokens(requestBody); - } -} - // Qwen API 服务适配器 export class QwenApiServiceAdapter extends ApiServiceAdapter { constructor(config) { @@ -663,9 +603,6 @@ export function getServiceAdapter(config) { case MODEL_PROVIDER.IFLOW_API: serviceInstances[providerKey] = new IFlowApiServiceAdapter(config); break; - case MODEL_PROVIDER.ORCHIDS_API: - serviceInstances[providerKey] = new OrchidsApiServiceAdapter(config); - break; case MODEL_PROVIDER.CODEX_API: serviceInstances[providerKey] = new CodexApiServiceAdapter(config); break; diff --git a/src/providers/provider-models.js b/src/providers/provider-models.js index 2b6ac3d..db55437 100644 --- a/src/providers/provider-models.js +++ b/src/providers/provider-models.js @@ -33,13 +33,6 @@ export const PROVIDER_MODELS = { 'claude-sonnet-4-20250514', 'claude-3-7-sonnet-20250219' ], - 'claude-orchids-oauth': [ - 'claude-sonnet-4-5', - 'claude-opus-4-5', - 'claude-haiku-4-5', - 'gemini-3-flash', - 'gpt-5.2' - ], 'openai-custom': [], 'openaiResponses-custom': [], 'openai-qwen-oauth': [ diff --git a/src/providers/provider-pool-manager.js b/src/providers/provider-pool-manager.js index ce631fd..e1b5654 100644 --- a/src/providers/provider-pool-manager.js +++ b/src/providers/provider-pool-manager.js @@ -17,7 +17,6 @@ export class ProviderPoolManager { 'openai-custom': 'gpt-3.5-turbo', 'claude-custom': 'claude-3-7-sonnet-20250219', 'claude-kiro-oauth': 'claude-haiku-4-5', - 'claude-orchids-oauth': 'claude-haiku-4-5', 'openai-qwen-oauth': 'qwen3-coder-flash', 'openai-iflow': 'qwen3-coder-plus', 'openai-codex-oauth': 'gpt-5-codex-mini', @@ -98,8 +97,6 @@ export class ProviderPoolManager { configPath = config.IFLOW_OAUTH_CREDS_FILE_PATH; } else if (providerType.startsWith('openai-codex')) { configPath = config.CODEX_OAUTH_CREDS_FILE_PATH; - } else if (providerType.startsWith('claude-orchids')) { - configPath = config.ORCHIDS_CREDS_FILE_PATH; } // console.log(`Checking node ${providerStatus.uuid} (${providerType}) expiry date... configPath: ${configPath}`); diff --git a/src/services/ui-manager.js b/src/services/ui-manager.js index aa3989d..6a5368a 100644 --- a/src/services/ui-manager.js +++ b/src/services/ui-manager.js @@ -300,11 +300,6 @@ export async function handleUIApiRequests(method, pathParam, req, res, currentCo return await oauthApi.handleImportAwsCredentials(req, res); } - // Import Orchids token - if (method === 'POST' && pathParam === '/api/orchids/import-token') { - return await oauthApi.handleImportOrchidsToken(req, res); - } - // Get plugins list if (method === 'GET' && pathParam === '/api/plugins') { return await pluginApi.handleGetPlugins(req, res); diff --git a/src/ui-modules/config-scanner.js b/src/ui-modules/config-scanner.js index 042740f..984b112 100644 --- a/src/ui-modules/config-scanner.js +++ b/src/ui-modules/config-scanner.js @@ -28,7 +28,6 @@ export async function scanConfigFiles(currentConfig, providerPoolManager) { addToUsedPaths(usedPaths, currentConfig.QWEN_OAUTH_CREDS_FILE_PATH); addToUsedPaths(usedPaths, currentConfig.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH); addToUsedPaths(usedPaths, currentConfig.IFLOW_TOKEN_FILE_PATH); - addToUsedPaths(usedPaths, currentConfig.ORCHIDS_CREDS_FILE_PATH); addToUsedPaths(usedPaths, currentConfig.CODEX_OAUTH_CREDS_FILE_PATH); // 使用最新的提供商池数据 @@ -46,7 +45,6 @@ export async function scanConfigFiles(currentConfig, providerPoolManager) { addToUsedPaths(usedPaths, provider.QWEN_OAUTH_CREDS_FILE_PATH); addToUsedPaths(usedPaths, provider.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH); addToUsedPaths(usedPaths, provider.IFLOW_TOKEN_FILE_PATH); - addToUsedPaths(usedPaths, provider.ORCHIDS_CREDS_FILE_PATH); addToUsedPaths(usedPaths, provider.CODEX_OAUTH_CREDS_FILE_PATH); } } @@ -218,17 +216,6 @@ function getFileUsageInfo(relativePath, fileName, usedPaths, currentConfig) { }); } - if (currentConfig.ORCHIDS_CREDS_FILE_PATH && - (pathsEqual(relativePath, currentConfig.ORCHIDS_CREDS_FILE_PATH) || - pathsEqual(relativePath, currentConfig.ORCHIDS_CREDS_FILE_PATH.replace(/\\/g, '/')))) { - usageInfo.usageType = 'main_config'; - usageInfo.usageDetails.push({ - type: 'Main Config', - location: 'Orchids OAuth credentials file path', - configKey: 'ORCHIDS_CREDS_FILE_PATH' - }); - } - if (currentConfig.CODEX_OAUTH_CREDS_FILE_PATH && (pathsEqual(relativePath, currentConfig.CODEX_OAUTH_CREDS_FILE_PATH) || pathsEqual(relativePath, currentConfig.CODEX_OAUTH_CREDS_FILE_PATH.replace(/\\/g, '/')))) { @@ -311,18 +298,6 @@ function getFileUsageInfo(relativePath, fileName, usedPaths, currentConfig) { }); } - if (provider.ORCHIDS_CREDS_FILE_PATH && - (pathsEqual(relativePath, provider.ORCHIDS_CREDS_FILE_PATH) || - pathsEqual(relativePath, provider.ORCHIDS_CREDS_FILE_PATH.replace(/\\/g, '/')))) { - providerUsages.push({ - type: 'Provider Pool', - location: `Orchids OAuth credentials (node ${index + 1})`, - providerType: providerType, - providerIndex: index, - configKey: 'ORCHIDS_CREDS_FILE_PATH' - }); - } - if (provider.CODEX_OAUTH_CREDS_FILE_PATH && (pathsEqual(relativePath, provider.CODEX_OAUTH_CREDS_FILE_PATH) || pathsEqual(relativePath, provider.CODEX_OAUTH_CREDS_FILE_PATH.replace(/\\/g, '/')))) { diff --git a/src/ui-modules/oauth-api.js b/src/ui-modules/oauth-api.js index 458e625..1dd2c1a 100644 --- a/src/ui-modules/oauth-api.js +++ b/src/ui-modules/oauth-api.js @@ -5,11 +5,9 @@ import { handleQwenOAuth, handleKiroOAuth, handleIFlowOAuth, - handleOrchidsOAuth, handleCodexOAuth, batchImportKiroRefreshTokensStream, - importAwsCredentials, - importOrchidsToken + importAwsCredentials } from '../auth/oauth-handlers.js'; /** @@ -52,11 +50,6 @@ export async function handleGenerateAuthUrl(req, res, currentConfig, providerTyp const result = await handleIFlowOAuth(currentConfig, options); authUrl = result.authUrl; authInfo = result.authInfo; - } else if (providerType === 'claude-orchids-oauth') { - // Orchids OAuth(手动导入模式) - const result = await handleOrchidsOAuth(currentConfig, options); - authUrl = result.authUrl; - authInfo = result.authInfo; } else if (providerType === 'openai-codex-oauth') { // Codex OAuth(OAuth2 + PKCE) const result = await handleCodexOAuth(currentConfig, options); @@ -328,285 +321,3 @@ export async function handleImportAwsCredentials(req, res) { return true; } } - -/** - * 导入 Orchids Token - * 支持三种格式: - * 1. cookieString 格式 (完整的 Cookie 字符串,包含 __client 和 __session) - * 2. token 字符串格式 (JWT|rotating_token) - 已废弃 - * 3. credentials 对象格式 (cookies, clerkSessionId, userId, workingDir) - */ -export async function handleImportOrchidsToken(req, res) { - try { - const body = await getRequestBody(req); - const { token, credentials, workingDir, cookieString } = body; - - // 新格式:完整的 Cookie 字符串 - if (cookieString && typeof cookieString === 'string') { - console.log('[Orchids Import] Starting cookie string import...'); - - // 解析 Cookie 字符串 - const parsedResult = parseOrchidsCookieString(cookieString); - if (!parsedResult.success) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - success: false, - error: parsedResult.error - })); - return true; - } - - // 保存凭据 - const result = await saveOrchidsCredentials(parsedResult.credentials); - - if (result.success) { - console.log(`[Orchids Import] Successfully imported credentials to: ${result.path}`); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - success: true, - path: result.path, - sessionId: result.sessionId, - userId: result.userId, - message: 'Orchids credentials imported successfully' - })); - } else { - const statusCode = result.error === 'duplicate' ? 409 : 500; - res.writeHead(statusCode, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - success: false, - error: result.error, - existingPath: result.existingPath || null - })); - } - return true; - } - - // 如果提供了 credentials 对象,直接保存 - if (credentials && typeof credentials === 'object') { - console.log('[Orchids Import] Starting credentials import...'); - - // 验证必需字段 - if (!credentials.cookies && (!credentials.clerkSessionId)) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - success: false, - error: 'credentials must contain cookies or clerkSessionId' - })); - return true; - } - - // 直接保存凭据 - const result = await saveOrchidsCredentials(credentials); - - if (result.success) { - console.log(`[Orchids Import] Successfully imported token to: ${result.path}`); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - success: true, - path: result.path, - sessionId: result.sessionId, - userId: result.userId, - message: 'Orchids credentials imported successfully' - })); - } else { - const statusCode = result.error === 'duplicate' ? 409 : 500; - res.writeHead(statusCode, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - success: false, - error: result.error, - existingPath: result.existingPath || null - })); - } - return true; - } - - // 原有的 token 字符串格式 - if (!token || typeof token !== 'string') { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - success: false, - error: 'cookieString, token string or credentials object is required' - })); - return true; - } - - console.log('[Orchids Import] Starting token import...'); - - const result = await importOrchidsToken(token, { workingDir }); - - if (result.success) { - console.log(`[Orchids Import] Successfully imported token to: ${result.path}`); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - success: true, - path: result.path, - sessionId: result.sessionId, - userId: result.userId, - message: 'Orchids token imported successfully' - })); - } else { - res.writeHead(500, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - success: false, - error: result.error - })); - } - return true; - - } catch (error) { - console.error('[Orchids Import] Error:', error); - res.writeHead(500, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - success: false, - error: error.message - })); - return true; - } -} - -/** - * 直接保存 Orchids 凭据(从 UI 表单提交) - */ -async function saveOrchidsCredentials(credentials) { - const fs = await import('fs'); - const path = await import('path'); - const { broadcastEvent } = await import('../services/ui-manager.js'); - const { autoLinkProviderConfigs } = await import('../services/service-manager.js'); - const { CONFIG } = await import('../core/config-manager.js'); - - try { - // 准备凭据数据 - const credentialsData = { - cookies: credentials.cookies || '', - clerkSessionId: credentials.clerkSessionId || `sess_${Date.now()}`, - userId: credentials.userId || 'user_unknown', - workingDir: credentials.workingDir || 'E:\\path\\to\\default\\project', - expiresAt: credentials.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), - importedAt: new Date().toISOString() - }; - - // 生成文件路径: configs/orchids/{timestamp}_orchids_creds/{timestamp}_orchids_creds.json - // 与 importOrchidsToken 保持一致的目录结构 - const timestamp = Date.now(); - const folderName = `${timestamp}_orchids_creds`; - const targetDir = path.default.join(process.cwd(), 'configs', 'orchids', folderName); - await fs.promises.mkdir(targetDir, { recursive: true }); - - const filename = `${folderName}.json`; - const credPath = path.default.join(targetDir, filename); - await fs.promises.writeFile(credPath, JSON.stringify(credentialsData, null, 2)); - - const relativePath = path.default.relative(process.cwd(), credPath); - - console.log(`[Orchids Import] Credentials saved to: ${relativePath}`); - - // 广播事件 - broadcastEvent('oauth_success', { - provider: 'claude-orchids-oauth', - relativePath: relativePath, - timestamp: new Date().toISOString() - }); - - // 自动关联新生成的凭据到 Pools - await autoLinkProviderConfigs(CONFIG); - - return { - success: true, - path: relativePath, - sessionId: credentialsData.clerkSessionId, - userId: credentialsData.userId - }; - - } catch (error) { - console.error('[Orchids Import] Save credentials failed:', error); - return { - success: false, - error: error.message - }; - } -} - -/** - * 解析 Orchids Cookie 字符串 - * 从完整的 Cookie 字符串中提取 __client、__session 和 clerkSessionId - * @param {string} cookieString - 完整的 Cookie 字符串 - * @returns {Object} 解析结果 - */ -function parseOrchidsCookieString(cookieString) { - try { - // 提取 __client cookie - const clientMatch = cookieString.match(/__client=([^;]+)/); - if (!clientMatch) { - return { success: false, error: 'Cookie 中缺少 __client' }; - } - const clientCookie = clientMatch[1].trim(); - - // 提取 __session cookie - const sessionMatch = cookieString.match(/__session=([^;]+)/); - if (!sessionMatch) { - return { success: false, error: 'Cookie 中缺少 __session' }; - } - const sessionCookie = sessionMatch[1].trim(); - - // 从 __session JWT 中解析 clerkSessionId (sid) 和 userId (sub) - let clerkSessionId = null; - let userId = 'user_unknown'; - - try { - const sessionParts = sessionCookie.split('.'); - if (sessionParts.length === 3) { - const payloadBase64 = sessionParts[1].replace(/-/g, '+').replace(/_/g, '/'); - const payloadJson = Buffer.from(payloadBase64, 'base64').toString('utf-8'); - const payload = JSON.parse(payloadJson); - - if (payload.sid) { - clerkSessionId = payload.sid; - } - if (payload.sub) { - userId = payload.sub; - } - } - } catch (e) { - console.warn('[Orchids Import] Failed to parse __session JWT:', e.message); - } - - // 如果无法从 __session 获取 sid,尝试从 __client 获取 - if (!clerkSessionId) { - try { - const clientParts = clientCookie.split('.'); - if (clientParts.length === 3) { - const payloadBase64 = clientParts[1].replace(/-/g, '+').replace(/_/g, '/'); - const payloadJson = Buffer.from(payloadBase64, 'base64').toString('utf-8'); - const payload = JSON.parse(payloadJson); - - // 从 client_id 推断 session_id - if (payload.id && payload.id.startsWith('client_')) { - clerkSessionId = 'sess_' + payload.id.substring(7); - } - } - } catch (e) { - console.warn('[Orchids Import] Failed to parse __client JWT:', e.message); - } - } - - if (!clerkSessionId) { - return { success: false, error: '无法从 Cookie 中提取 Session ID' }; - } - - // 构建 cookies 字符串(只保留 __client 和 __session) - const cookies = `__client=${clientCookie}; __session=${sessionCookie}`; - - return { - success: true, - credentials: { - cookies: cookies, - clerkSessionId: clerkSessionId, - userId: userId, - expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString() - } - }; - - } catch (error) { - return { success: false, error: `解析 Cookie 失败: ${error.message}` }; - } -} \ No newline at end of file diff --git a/src/utils/common.js b/src/utils/common.js index ab69952..1a1b6b2 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -66,7 +66,6 @@ export const MODEL_PROVIDER = { OPENAI_CUSTOM_RESPONSES: 'openaiResponses-custom', CLAUDE_CUSTOM: 'claude-custom', KIRO_API: 'claude-kiro-oauth', - ORCHIDS_API: 'claude-orchids-oauth', QWEN_API: 'openai-qwen-oauth', IFLOW_API: 'openai-iflow', CODEX_API: 'openai-codex-oauth', diff --git a/src/utils/provider-utils.js b/src/utils/provider-utils.js index f3e4630..d02eafc 100644 --- a/src/utils/provider-utils.js +++ b/src/utils/provider-utils.js @@ -66,17 +66,6 @@ export const PROVIDER_MAPPINGS = [ needsProjectId: false, urlKeys: ['IFLOW_BASE_URL'] }, - { - // Orchids OAuth 配置 - dirName: 'orchids', - patterns: ['configs/orchids/', '/orchids/'], - providerType: 'claude-orchids-oauth', - credPathKey: 'ORCHIDS_CREDS_FILE_PATH', - defaultCheckModel: 'claude-sonnet-4-5', - displayName: 'Orchids OAuth', - needsProjectId: false, - urlKeys: ['ORCHIDS_BASE_URL'] - }, { // Codex OAuth 配置 dirName: 'codex', diff --git a/static/app/i18n.js b/static/app/i18n.js index 5fc7730..a1adc79 100644 --- a/static/app/i18n.js +++ b/static/app/i18n.js @@ -89,7 +89,6 @@ const translations = { 'dashboard.routing.nodeName.openai': 'OpenAI Custom', 'dashboard.routing.nodeName.qwen': 'Qwen OAuth', 'dashboard.routing.nodeName.iflow': 'iFlow OAuth', - 'dashboard.routing.nodeName.orchids': 'Orchids OAuth', 'dashboard.routing.nodeName.codex': 'OpenAI Codex OAuth', 'dashboard.contact.title': '联系与赞助', 'dashboard.contact.wechat': '扫码进群,注明来意', @@ -188,32 +187,6 @@ const translations = { 'oauth.iflow.step2': '使用您的 iFlow 账号登录并授权', 'oauth.iflow.step3': '授权完成后,系统会自动获取 API Key', 'oauth.iflow.step4': '凭据文件可在上传配置管理中查看和管理', - - // Orchids OAuth - 'oauth.orchids.title': 'Orchids 凭据导入', - 'oauth.orchids.formatToken': 'JWT Token 格式', - 'oauth.orchids.tokenLabel': 'JWT Token 字符串', - 'oauth.orchids.tokenPlaceholder': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNsaWVudF94eHgiLCJyb3RhdGluZ190b2tlbiI6Inh4eCJ9.xxx', - 'oauth.orchids.tokenInstructions': '粘贴 JWT Token 字符串(支持纯 JWT 格式,rotating_token 会从 JWT payload 中自动提取)', - 'oauth.orchids.getSteps': '获取步骤:', - 'oauth.orchids.tokenStep1': '访问 www.orchids.app 并登录', - 'oauth.orchids.tokenStep2': '按 F12 打开开发者工具 → Application 标签', - 'oauth.orchids.tokenStep3': '在左侧找到 Cookies → www.orchids.app', - 'oauth.orchids.tokenStep4': '找到 __client cookie 的值', - 'oauth.orchids.tokenStep5': '复制完整的 JWT 值(以 eyJ 开头的字符串)', - 'oauth.orchids.tokenFormat': '格式:eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNsaWVudF94eHgiLCJyb3RhdGluZ190b2tlbiI6Inh4eCJ9.xxx', - 'oauth.orchids.confirmImport': '确认导入', - 'oauth.orchids.importing': '导入中...', - 'oauth.orchids.success': 'Orchids 凭据导入成功', - 'oauth.orchids.parseSuccess': 'Token 解析成功', - 'oauth.orchids.detectedToken': '检测到有效的 JWT Token', - 'oauth.orchids.errorEmpty': '请输入凭据', - 'oauth.orchids.errorTokenInvalid': 'Token 格式错误,请输入有效的 JWT Token', - 'oauth.orchids.errorJwtParse': 'JWT 解析失败', - 'oauth.orchids.errorMissingRotating': 'JWT payload 中缺少 rotating_token 字段', - 'oauth.orchids.importFailed': '导入失败', - 'oauth.orchids.clientId': 'Client ID', - 'oauth.orchids.rotatingToken': 'Rotating Token', // Config 'config.title': '配置管理', @@ -335,7 +308,6 @@ const translations = { 'upload.providerFilter.gemini': 'Gemini OAuth', 'upload.providerFilter.qwen': 'Qwen OAuth', 'upload.providerFilter.antigravity': 'Antigravity', - 'upload.providerFilter.orchids': 'Orchids OAuth', 'upload.providerFilter.codex': 'Codex OAuth', 'upload.providerFilter.iflow': 'iFlow OAuth', 'upload.providerFilter.other': '其他/未识别', @@ -574,7 +546,6 @@ const translations = { 'guide.providers.claude.desc': '使用 Claude 官方 API 或第三方代理访问 Claude 系列模型', 'guide.providers.openai.desc': '使用 OpenAI 官方 API 或第三方代理访问 GPT 系列模型', 'guide.providers.iflow.desc': '通过 iFlow OAuth 认证访问 Qwen、Kimi、DeepSeek、GLM 等模型', - 'guide.providers.orchids.desc': '通过 Orchids 平台免费使用 Claude Sonnet 4.5 等模型', 'guide.client.title': '客户端配置指南', 'guide.client.desc': '以下是常见 AI 客户端的配置方法,将 API 端点设置为本服务地址即可使用:', 'guide.client.cherry.step1': '打开设置 → 模型服务商', @@ -802,7 +773,6 @@ const translations = { 'dashboard.routing.nodeName.openai': 'OpenAI Custom', 'dashboard.routing.nodeName.qwen': 'Qwen OAuth', 'dashboard.routing.nodeName.iflow': 'iFlow OAuth', - 'dashboard.routing.nodeName.orchids': 'Orchids OAuth', 'dashboard.routing.nodeName.codex': 'OpenAI Codex OAuth', 'dashboard.contact.title': 'Contact & Support', 'dashboard.contact.wechat': 'Scan to Join Group', @@ -901,32 +871,6 @@ const translations = { 'oauth.iflow.step2': 'Log in with your iFlow account and authorize', 'oauth.iflow.step3': 'After authorization, the system will automatically fetch the API Key', 'oauth.iflow.step4': 'Credentials files can be viewed and managed in Upload Config', - - // Orchids OAuth - 'oauth.orchids.title': 'Orchids Credential Import', - 'oauth.orchids.formatToken': 'JWT Token Format', - 'oauth.orchids.tokenLabel': 'JWT Token String', - 'oauth.orchids.tokenPlaceholder': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNsaWVudF94eHgiLCJyb3RhdGluZ190b2tlbiI6Inh4eCJ9.xxx', - 'oauth.orchids.tokenInstructions': 'Paste JWT Token string (rotating_token will be automatically extracted from JWT payload)', - 'oauth.orchids.getSteps': 'How to get:', - 'oauth.orchids.tokenStep1': 'Visit www.orchids.app and log in', - 'oauth.orchids.tokenStep2': 'Press F12 to open Developer Tools → Application tab', - 'oauth.orchids.tokenStep3': 'Find Cookies → www.orchids.app on the left', - 'oauth.orchids.tokenStep4': 'Find the __client cookie value', - 'oauth.orchids.tokenStep5': 'Copy the full JWT value (string starting with eyJ)', - 'oauth.orchids.tokenFormat': 'Format: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNsaWVudF94eHgiLCJyb3RhdGluZ190b2tlbiI6Inh4eCJ9.xxx', - 'oauth.orchids.confirmImport': 'Confirm Import', - 'oauth.orchids.importing': 'Importing...', - 'oauth.orchids.success': 'Orchids credentials imported successfully', - 'oauth.orchids.parseSuccess': 'Token parsed successfully', - 'oauth.orchids.detectedToken': 'Valid JWT Token detected', - 'oauth.orchids.errorEmpty': 'Please enter credentials', - 'oauth.orchids.errorTokenInvalid': 'Token format error, please enter a valid JWT Token', - 'oauth.orchids.errorJwtParse': 'JWT parse failed', - 'oauth.orchids.errorMissingRotating': 'Missing rotating_token field in JWT payload', - 'oauth.orchids.importFailed': 'Import failed', - 'oauth.orchids.clientId': 'Client ID', - 'oauth.orchids.rotatingToken': 'Rotating Token', // Config 'config.title': 'Configuration Management', @@ -1048,7 +992,6 @@ const translations = { 'upload.providerFilter.gemini': 'Gemini OAuth', 'upload.providerFilter.qwen': 'Qwen OAuth', 'upload.providerFilter.antigravity': 'Antigravity', - 'upload.providerFilter.orchids': 'Orchids OAuth', 'upload.providerFilter.codex': 'Codex OAuth', 'upload.providerFilter.iflow': 'iFlow OAuth', 'upload.providerFilter.other': 'Other/Unknown', @@ -1287,7 +1230,6 @@ const translations = { 'guide.providers.claude.desc': 'Access Claude models via official API or third-party proxy', 'guide.providers.openai.desc': 'Access GPT models via official API or third-party proxy', 'guide.providers.iflow.desc': 'Access Qwen, Kimi, DeepSeek, GLM via iFlow OAuth', - 'guide.providers.orchids.desc': 'Free access to Claude Sonnet 4.5 via Orchids platform', 'guide.client.title': 'Client Configuration Guide', 'guide.client.desc': 'Here are configuration methods for common AI clients. Set the API endpoint to this service address:', 'guide.client.cherry.step1': 'Open Settings → Model Providers', diff --git a/static/app/models-manager.js b/static/app/models-manager.js index 5c562b3..15682ff 100644 --- a/static/app/models-manager.js +++ b/static/app/models-manager.js @@ -166,7 +166,6 @@ function getProviderDisplayName(providerType) { 'gemini-antigravity': 'Gemini Antigravity', 'claude-custom': 'Claude Custom', 'claude-kiro-oauth': 'Claude Kiro (OAuth)', - 'claude-orchids-oauth': 'Claude Orchids (OAuth)', 'openai-custom': 'OpenAI Custom', 'openaiResponses-custom': 'OpenAI Responses Custom', 'openai-qwen-oauth': 'Qwen (OAuth)', diff --git a/static/app/provider-manager.js b/static/app/provider-manager.js index f3bec46..3e3ad32 100644 --- a/static/app/provider-manager.js +++ b/static/app/provider-manager.js @@ -212,7 +212,6 @@ function renderProviders(providers) { 'openai-custom', 'claude-custom', 'claude-kiro-oauth', - 'claude-orchids-oauth', 'openai-qwen-oauth', 'openaiResponses-custom', 'openai-iflow', @@ -425,22 +424,12 @@ async function openProviderManager(providerType) { */ function generateAuthButton(providerType) { // 只为支持OAuth的提供商显示授权按钮 - const oauthProviders = ['gemini-cli-oauth', 'gemini-antigravity', 'openai-qwen-oauth', 'claude-kiro-oauth', 'claude-orchids-oauth', 'openai-iflow', 'openai-codex-oauth']; + const oauthProviders = ['gemini-cli-oauth', 'gemini-antigravity', 'openai-qwen-oauth', 'claude-kiro-oauth', 'openai-iflow', 'openai-codex-oauth']; if (!oauthProviders.includes(providerType)) { return ''; } - // Orchids 提供商使用不同的按钮文本 - if (providerType === 'claude-orchids-oauth') { - return ` - - `; - } - // Codex 提供商使用特殊图标 if (providerType === 'openai-codex-oauth') { return ` @@ -470,217 +459,9 @@ async function handleGenerateAuthUrl(providerType) { return; } - // 如果是 Orchids OAuth,显示 Cookie 导入对话框 - if (providerType === 'claude-orchids-oauth') { - showOrchidsCookieImportDialog(providerType); - return; - } - await executeGenerateAuthUrl(providerType, {}); } -/** - * 显示 Orchids Token 导入对话框 - * 支持两种格式: - * 1. 纯 JWT 格式:eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...(JWT payload 中包含 rotating_token) - * 2. JWT|rotating_token 格式:eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...|W7lqx1t8HIxMh0ScDZUB - * @param {string} providerType - 提供商类型 - */ -function showOrchidsCookieImportDialog(providerType) { - const modal = document.createElement('div'); - modal.className = 'modal-overlay'; - modal.style.display = 'flex'; - - modal.innerHTML = ` - - `; - - document.body.appendChild(modal); - - const closeBtn = modal.querySelector('.modal-close'); - const cancelBtn = modal.querySelector('.modal-cancel'); - const submitBtn = modal.querySelector('#orchidsSubmitBtn'); - const tokenInput = modal.querySelector('#orchidsTokenInput'); - const validationResult = modal.querySelector('#orchidsValidationResult'); - const parsePreview = modal.querySelector('#orchidsParsePreview'); - const parseDetails = modal.querySelector('#orchidsParseDetails'); - - // 实时验证输入 - tokenInput.addEventListener('input', () => { - const inputValue = tokenInput.value.trim(); - if (!inputValue) { - validationResult.style.display = 'none'; - parsePreview.style.display = 'none'; - return; - } - - // 检测 JWT 格式 - if (inputValue.startsWith('eyJ') && inputValue.split('.').length === 3) { - // 尝试解析 JWT - try { - let jwt = inputValue; - let rotatingTokenFromSeparator = null; - - // 检查是否有 | 分隔符 - if (inputValue.includes('|')) { - const parts = inputValue.split('|'); - jwt = parts[0]; - rotatingTokenFromSeparator = parts[1]; - } - - const jwtParts = jwt.split('.'); - if (jwtParts.length === 3) { - let payloadBase64 = jwtParts[1].replace(/-/g, '+').replace(/_/g, '/'); - // 添加 padding - while (payloadBase64.length % 4) { - payloadBase64 += '='; - } - const payloadJson = atob(payloadBase64); - const payload = JSON.parse(payloadJson); - - // 检查是否有 rotating_token(从 payload 或分隔符后) - const rotatingToken = payload.rotating_token || rotatingTokenFromSeparator; - - if (rotatingToken) { - validationResult.style.cssText = 'display: block; background: #f0fdf4; border: 1px solid #bbf7d0; color: #166534;'; - validationResult.innerHTML = ' ' + t('oauth.orchids.detectedToken'); - - parsePreview.style.display = 'block'; - parseDetails.innerHTML = ` -
-
${t('oauth.orchids.clientId')}: ${payload.id || 'N/A'}
-
${t('oauth.orchids.rotatingToken')}: ${rotatingToken.substring(0, 30)}...
-
- `; - } else { - validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;'; - validationResult.innerHTML = ' ' + t('oauth.orchids.errorMissingRotating'); - parsePreview.style.display = 'none'; - } - } - } catch (e) { - validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;'; - validationResult.innerHTML = ' ' + t('oauth.orchids.errorJwtParse') + ': ' + e.message; - parsePreview.style.display = 'none'; - } - } else { - validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;'; - validationResult.innerHTML = ' ' + t('oauth.orchids.errorTokenInvalid'); - parsePreview.style.display = 'none'; - } - }); - - // 关闭按钮事件 - [closeBtn, cancelBtn].forEach(btn => { - btn.addEventListener('click', () => modal.remove()); - }); - - // 提交按钮事件 - submitBtn.addEventListener('click', async () => { - const inputValue = tokenInput.value.trim(); - - // 验证输入 - if (!inputValue) { - validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;'; - validationResult.innerHTML = ' ' + t('oauth.orchids.errorEmpty'); - return; - } - - // JWT 格式验证(支持纯 JWT 和 JWT|rotating_token 格式) - let jwt = inputValue; - if (inputValue.includes('|')) { - jwt = inputValue.split('|')[0]; - } - - if (!jwt.startsWith('eyJ') || jwt.split('.').length !== 3) { - validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;'; - validationResult.innerHTML = ' ' + t('oauth.orchids.errorTokenInvalid'); - return; - } - - // 禁用按钮 - submitBtn.disabled = true; - submitBtn.innerHTML = ' ' + t('oauth.orchids.importing') + ''; - - try { - const response = await window.apiClient.post('/orchids/import-token', { token: inputValue }); - - if (response.success) { - showToast(t('common.success'), t('oauth.orchids.success'), 'success'); - modal.remove(); - loadProviders(); - loadConfigList(); - } else { - validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;'; - validationResult.innerHTML = ' ' + (response.error || t('oauth.orchids.importFailed')); - } - } catch (error) { - console.error('Orchids import failed:', error); - validationResult.style.cssText = 'display: block; background: #fef2f2; border: 1px solid #fecaca; color: #991b1b;'; - validationResult.innerHTML = ' ' + t('oauth.orchids.importFailed') + ': ' + error.message; - } finally { - submitBtn.disabled = false; - submitBtn.innerHTML = ' ' + t('oauth.orchids.confirmImport') + ''; - } - }); -} - /** * 显示 Kiro OAuth 认证方式选择对话框 * @param {string} providerType - 提供商类型 diff --git a/static/app/upload-config-manager.js b/static/app/upload-config-manager.js index 7399928..92c2a79 100644 --- a/static/app/upload-config-manager.js +++ b/static/app/upload-config-manager.js @@ -844,12 +844,6 @@ function detectProviderFromPath(filePath) { displayName: 'Gemini Antigravity', shortName: 'antigravity' }, - { - patterns: ['configs/orchids/', '/orchids/'], - providerType: 'claude-orchids-oauth', - displayName: 'Orchids OAuth', - shortName: 'orchids-oauth' - }, { patterns: ['configs/codex/', '/codex/'], providerType: 'openai-codex-oauth', diff --git a/static/components/header.html b/static/components/header.html index 210ba8e..40de0f1 100644 --- a/static/components/header.html +++ b/static/components/header.html @@ -7,8 +7,8 @@
- - KIRO账号购买 + + 多渠道账号购买 连接中... diff --git a/static/components/section-config.html b/static/components/section-config.html index 0d4f6a6..cc831c2 100644 --- a/static/components/section-config.html +++ b/static/components/section-config.html @@ -58,10 +58,6 @@ iFlow OAuth - -
-
-
- -

Orchids OAuth

- 突破限制/免费使用 -
-
- -
- - -
- - -
-
- - /claude-orchids-oauth/v1/chat/completions -
-
- -
curl http://localhost:3000/claude-orchids-oauth/v1/chat/completions \
-  -H "Content-Type: application/json" \
-  -H "Authorization: Bearer YOUR_API_KEY" \
-  -d '{
-    "model": "claude-sonnet-4-5",
-    "messages": [{"role": "user", "content": "Hello!"}],
-    "max_tokens": 8192
-  }'
-
-
- - -
-
- - /claude-orchids-oauth/v1/messages -
-
- -
curl http://localhost:3000/claude-orchids-oauth/v1/messages \
-  -H "Content-Type: application/json" \
-  -H "X-API-Key: YOUR_API_KEY" \
-  -d '{
-    "model": "claude-sonnet-4-5",
-    "max_tokens": 8192,
-    "messages": [{"role": "user", "content": "Hello!"}]
-  }'
-
-
-
-
-
diff --git a/static/components/section-guide.css b/static/components/section-guide.css index c608c71..e1b977e 100644 --- a/static/components/section-guide.css +++ b/static/components/section-guide.css @@ -334,7 +334,6 @@ .guide-provider-icon.claude { color: #d97706; } .guide-provider-icon.openai { color: #10a37f; } .guide-provider-icon.iflow { color: #3b82f6; } -.guide-provider-icon.orchids { color: #22c55e; } .guide-provider-name { font-weight: 600; diff --git a/static/components/section-upload-config.html b/static/components/section-upload-config.html index e2bbd88..b2e3f2c 100644 --- a/static/components/section-upload-config.html +++ b/static/components/section-upload-config.html @@ -23,7 +23,6 @@ -