From 91ad0658a9cbe7dff235ba00f2e1cff33c729082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E5=BA=86=E9=9B=B7?= Date: Wed, 12 Nov 2025 14:32:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0listExperiments=E7=AB=AF?= =?UTF-8?q?=E7=82=B9=E5=92=8C=E9=80=9A=E7=94=A8=E8=BD=AC=E5=8F=91=E6=9C=BA?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加forwardToCodeAssist通用转发函数支持简单端点 - 添加handleSimpleEndpoint通用路由处理函数 - 注册listExperiments路由(v1internal和v1beta) - 解决gemini-cli启动时404 Not Found错误 --- src/routes/geminiRoutes.js | 65 ++++++++++++++++++++++++++++ src/services/geminiAccountService.js | 42 ++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/src/routes/geminiRoutes.js b/src/routes/geminiRoutes.js index 8ece60fd..78a44bd3 100644 --- a/src/routes/geminiRoutes.js +++ b/src/routes/geminiRoutes.js @@ -349,6 +349,65 @@ router.get('/key-info', authenticateApiKey, async (req, res) => { } }) +// 通用的简单端点处理函数(用于直接转发的端点) +// 适用于:listExperiments 等不需要特殊业务逻辑的端点 +async function handleSimpleEndpoint(apiMethod) { + return async (req, res) => { + try { + if (!ensureGeminiPermission(req, res)) { + return undefined + } + + const sessionHash = sessionHelper.generateSessionHash(req.body) + + // 从路径参数或请求体中获取模型名 + const requestedModel = req.body.model || req.params.modelName || 'gemini-2.5-flash' + const { accountId } = await unifiedGeminiScheduler.selectAccountForApiKey( + req.apiKey, + sessionHash, + requestedModel + ) + const account = await geminiAccountService.getAccount(accountId) + const { accessToken, refreshToken } = account + + const version = req.path.includes('v1beta') ? 'v1beta' : 'v1internal' + logger.info(`${apiMethod} request (${version})`, { + apiKeyId: req.apiKey?.id || 'unknown', + requestBody: req.body + }) + + // 解析账户的代理配置 + let proxyConfig = null + if (account.proxy) { + try { + proxyConfig = typeof account.proxy === 'string' ? JSON.parse(account.proxy) : account.proxy + } catch (e) { + logger.warn('Failed to parse proxy configuration:', e) + } + } + + const client = await geminiAccountService.getOauthClient(accessToken, refreshToken, proxyConfig) + + // 直接转发请求体,不做特殊处理 + const response = await geminiAccountService.forwardToCodeAssist( + client, + apiMethod, + req.body, + proxyConfig + ) + + res.json(response) + } catch (error) { + const version = req.path.includes('v1beta') ? 'v1beta' : 'v1internal' + logger.error(`Error in ${apiMethod} endpoint (${version})`, { error: error.message }) + res.status(500).json({ + error: 'Internal server error', + message: error.message + }) + } + } +} + // 共用的 loadCodeAssist 处理函数 async function handleLoadCodeAssist(req, res) { try { @@ -1040,6 +1099,7 @@ router.post('/v1internal\\:onboardUser', authenticateApiKey, handleOnboardUser) router.post('/v1internal\\:countTokens', authenticateApiKey, handleCountTokens) router.post('/v1internal\\:generateContent', authenticateApiKey, handleGenerateContent) router.post('/v1internal\\:streamGenerateContent', authenticateApiKey, handleStreamGenerateContent) +router.post('/v1internal\\:listExperiments', authenticateApiKey, handleSimpleEndpoint('listExperiments')) // v1beta 版本的端点 - 支持动态模型名称 router.post('/v1beta/models/:modelName\\:loadCodeAssist', authenticateApiKey, handleLoadCodeAssist) @@ -1055,6 +1115,11 @@ router.post( authenticateApiKey, handleStreamGenerateContent ) +router.post( + '/v1beta/models/:modelName\\:listExperiments', + authenticateApiKey, + handleSimpleEndpoint('listExperiments') +) // 导出处理函数供标准路由使用 module.exports = router diff --git a/src/services/geminiAccountService.js b/src/services/geminiAccountService.js index 169df32e..e50d1744 100644 --- a/src/services/geminiAccountService.js +++ b/src/services/geminiAccountService.js @@ -1060,6 +1060,47 @@ async function getOauthClient(accessToken, refreshToken, proxyConfig = null) { return client } +// 通用的 Code Assist API 转发函数(用于简单的请求/响应端点) +// 适用于:loadCodeAssist, onboardUser, countTokens, listExperiments 等不需要特殊处理的端点 +async function forwardToCodeAssist(client, apiMethod, requestBody, proxyConfig = null) { + const axios = require('axios') + const CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com' + const CODE_ASSIST_API_VERSION = 'v1internal' + + const { token } = await client.getAccessToken() + const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig) + + logger.info(`📡 ${apiMethod} API调用开始`) + + const axiosConfig = { + url: `${CODE_ASSIST_ENDPOINT}/${CODE_ASSIST_API_VERSION}:${apiMethod}`, + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + data: requestBody, + timeout: 30000 + } + + // 添加代理配置 + if (proxyAgent) { + axiosConfig.httpAgent = proxyAgent + axiosConfig.httpsAgent = proxyAgent + axiosConfig.proxy = false + logger.info( + `🌐 Using proxy for ${apiMethod}: ${ProxyHelper.getProxyDescription(proxyConfig)}` + ) + } else { + logger.debug(`🌐 No proxy configured for ${apiMethod}`) + } + + const response = await axios(axiosConfig) + + logger.info(`✅ ${apiMethod} API调用成功`) + return response.data +} + // 调用 Google Code Assist API 的 loadCodeAssist 方法(支持代理) async function loadCodeAssist(client, projectId = null, proxyConfig = null) { const axios = require('axios') @@ -1529,6 +1570,7 @@ module.exports = { getAccountRateLimitInfo, isTokenExpired, getOauthClient, + forwardToCodeAssist, // 通用转发函数 loadCodeAssist, getOnboardTier, onboardUser,