mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-24 09:41:17 +00:00
Merge pull request #1 from zengqinglei/fix-gemini-cli-proxy-issues
Fix gemini cli proxy issues
This commit is contained in:
@@ -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 处理函数
|
// 共用的 loadCodeAssist 处理函数
|
||||||
async function handleLoadCodeAssist(req, res) {
|
async function handleLoadCodeAssist(req, res) {
|
||||||
try {
|
try {
|
||||||
@@ -1040,6 +1099,7 @@ router.post('/v1internal\\:onboardUser', authenticateApiKey, handleOnboardUser)
|
|||||||
router.post('/v1internal\\:countTokens', authenticateApiKey, handleCountTokens)
|
router.post('/v1internal\\:countTokens', authenticateApiKey, handleCountTokens)
|
||||||
router.post('/v1internal\\:generateContent', authenticateApiKey, handleGenerateContent)
|
router.post('/v1internal\\:generateContent', authenticateApiKey, handleGenerateContent)
|
||||||
router.post('/v1internal\\:streamGenerateContent', authenticateApiKey, handleStreamGenerateContent)
|
router.post('/v1internal\\:streamGenerateContent', authenticateApiKey, handleStreamGenerateContent)
|
||||||
|
router.post('/v1internal\\:listExperiments', authenticateApiKey, handleSimpleEndpoint('listExperiments'))
|
||||||
|
|
||||||
// v1beta 版本的端点 - 支持动态模型名称
|
// v1beta 版本的端点 - 支持动态模型名称
|
||||||
router.post('/v1beta/models/:modelName\\:loadCodeAssist', authenticateApiKey, handleLoadCodeAssist)
|
router.post('/v1beta/models/:modelName\\:loadCodeAssist', authenticateApiKey, handleLoadCodeAssist)
|
||||||
@@ -1055,6 +1115,11 @@ router.post(
|
|||||||
authenticateApiKey,
|
authenticateApiKey,
|
||||||
handleStreamGenerateContent
|
handleStreamGenerateContent
|
||||||
)
|
)
|
||||||
|
router.post(
|
||||||
|
'/v1beta/models/:modelName\\:listExperiments',
|
||||||
|
authenticateApiKey,
|
||||||
|
handleSimpleEndpoint('listExperiments')
|
||||||
|
)
|
||||||
|
|
||||||
// 导出处理函数供标准路由使用
|
// 导出处理函数供标准路由使用
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|||||||
@@ -1060,6 +1060,47 @@ async function getOauthClient(accessToken, refreshToken, proxyConfig = null) {
|
|||||||
return client
|
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 方法(支持代理)
|
// 调用 Google Code Assist API 的 loadCodeAssist 方法(支持代理)
|
||||||
async function loadCodeAssist(client, projectId = null, proxyConfig = null) {
|
async function loadCodeAssist(client, projectId = null, proxyConfig = null) {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
@@ -1069,54 +1110,10 @@ async function loadCodeAssist(client, projectId = null, proxyConfig = null) {
|
|||||||
const { token } = await client.getAccessToken()
|
const { token } = await client.getAccessToken()
|
||||||
const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig)
|
const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig)
|
||||||
|
|
||||||
const tokenInfoConfig = {
|
|
||||||
url: 'https://oauth2.googleapis.com/tokeninfo',
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
|
||||||
},
|
|
||||||
data: new URLSearchParams({ access_token: token }).toString(),
|
|
||||||
timeout: 15000
|
|
||||||
}
|
|
||||||
|
|
||||||
if (proxyAgent) {
|
|
||||||
tokenInfoConfig.httpAgent = proxyAgent
|
|
||||||
tokenInfoConfig.httpsAgent = proxyAgent
|
|
||||||
tokenInfoConfig.proxy = false
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await axios(tokenInfoConfig)
|
|
||||||
logger.info('📋 tokeninfo 接口验证成功')
|
|
||||||
} catch (error) {
|
|
||||||
logger.info('tokeninfo 接口获取失败', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const userInfoConfig = {
|
|
||||||
url: 'https://www.googleapis.com/oauth2/v2/userinfo',
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
Accept: '*/*'
|
|
||||||
},
|
|
||||||
timeout: 15000
|
|
||||||
}
|
|
||||||
|
|
||||||
if (proxyAgent) {
|
|
||||||
userInfoConfig.httpAgent = proxyAgent
|
|
||||||
userInfoConfig.httpsAgent = proxyAgent
|
|
||||||
userInfoConfig.proxy = false
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await axios(userInfoConfig)
|
|
||||||
logger.info('📋 userinfo 接口获取成功')
|
|
||||||
} catch (error) {
|
|
||||||
logger.info('userinfo 接口获取失败', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建ClientMetadata
|
// 创建ClientMetadata
|
||||||
|
// Note: 移除了 tokeninfo 和 userinfo 验证调用
|
||||||
|
// 这些调用在原生 gemini-cli 的 CodeAssistServer.loadCodeAssist 中不存在
|
||||||
|
// 且会导致使用特殊 token 时出现 401 错误
|
||||||
const clientMetadata = {
|
const clientMetadata = {
|
||||||
ideType: 'IDE_UNSPECIFIED',
|
ideType: 'IDE_UNSPECIFIED',
|
||||||
platform: 'PLATFORM_UNSPECIFIED',
|
platform: 'PLATFORM_UNSPECIFIED',
|
||||||
@@ -1573,6 +1570,7 @@ module.exports = {
|
|||||||
getAccountRateLimitInfo,
|
getAccountRateLimitInfo,
|
||||||
isTokenExpired,
|
isTokenExpired,
|
||||||
getOauthClient,
|
getOauthClient,
|
||||||
|
forwardToCodeAssist, // 通用转发函数
|
||||||
loadCodeAssist,
|
loadCodeAssist,
|
||||||
getOnboardTier,
|
getOnboardTier,
|
||||||
onboardUser,
|
onboardUser,
|
||||||
|
|||||||
Reference in New Issue
Block a user