mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 19:24:51 +00:00
fix: 修复gemini转发问题
This commit is contained in:
@@ -3624,8 +3624,8 @@ router.post('/bedrock-accounts', authenticateAdmin, async (req, res) => {
|
||||
}
|
||||
|
||||
logger.success(`☁️ Admin created Bedrock account: ${name}`)
|
||||
const formattedAccount = formatAccountExpiry(formattedAccount)
|
||||
return res.json({ success: true, data: result.data })
|
||||
const formattedAccount = formatAccountExpiry(result.data)
|
||||
return res.json({ success: true, data: formattedAccount })
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to create Bedrock account:', error)
|
||||
return res
|
||||
@@ -4078,8 +4078,8 @@ router.post('/gemini-accounts', authenticateAdmin, async (req, res) => {
|
||||
}
|
||||
|
||||
logger.success(`🏢 Admin created new Gemini account: ${accountData.name}`)
|
||||
const formattedAccount = formatAccountExpiry(formattedAccount)
|
||||
return res.json({ success: true, data: newAccount })
|
||||
const formattedAccount = formatAccountExpiry(newAccount)
|
||||
return res.json({ success: true, data: formattedAccount })
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to create Gemini account:', error)
|
||||
return res.status(500).json({ error: 'Failed to create account', message: error.message })
|
||||
|
||||
@@ -44,6 +44,90 @@ function ensureGeminiPermissionMiddleware(req, res, next) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// 判断对象是否为可读流
|
||||
function isReadableStream(value) {
|
||||
return value && typeof value.on === 'function' && typeof value.pipe === 'function'
|
||||
}
|
||||
|
||||
// 读取可读流内容为字符串
|
||||
async function readStreamToString(stream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let result = ''
|
||||
|
||||
try {
|
||||
if (typeof stream.setEncoding === 'function') {
|
||||
stream.setEncoding('utf8')
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('设置流编码失败:', error)
|
||||
}
|
||||
|
||||
stream.on('data', (chunk) => {
|
||||
result += chunk
|
||||
})
|
||||
|
||||
stream.on('end', () => {
|
||||
resolve(result)
|
||||
})
|
||||
|
||||
stream.on('error', (error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 规范化上游 Axios 错误信息
|
||||
async function normalizeAxiosStreamError(error) {
|
||||
const status = error.response?.status
|
||||
const statusText = error.response?.statusText
|
||||
const responseData = error.response?.data
|
||||
let rawBody = null
|
||||
let parsedBody = null
|
||||
|
||||
if (responseData) {
|
||||
try {
|
||||
if (isReadableStream(responseData)) {
|
||||
rawBody = await readStreamToString(responseData)
|
||||
} else if (Buffer.isBuffer(responseData)) {
|
||||
rawBody = responseData.toString('utf8')
|
||||
} else if (typeof responseData === 'string') {
|
||||
rawBody = responseData
|
||||
} else {
|
||||
rawBody = JSON.stringify(responseData)
|
||||
}
|
||||
} catch (streamError) {
|
||||
logger.warn('读取 Gemini 上游错误流失败:', streamError)
|
||||
}
|
||||
}
|
||||
|
||||
if (rawBody) {
|
||||
if (typeof rawBody === 'string') {
|
||||
try {
|
||||
parsedBody = JSON.parse(rawBody)
|
||||
} catch (parseError) {
|
||||
parsedBody = rawBody
|
||||
}
|
||||
} else {
|
||||
parsedBody = rawBody
|
||||
}
|
||||
}
|
||||
|
||||
let finalMessage = error.message || 'Internal server error'
|
||||
if (parsedBody && typeof parsedBody === 'object') {
|
||||
finalMessage = parsedBody.error?.message || parsedBody.message || finalMessage
|
||||
} else if (typeof parsedBody === 'string' && parsedBody.trim()) {
|
||||
finalMessage = parsedBody.trim()
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
statusText,
|
||||
message: finalMessage,
|
||||
parsedBody,
|
||||
rawBody
|
||||
}
|
||||
}
|
||||
|
||||
// 标准 Gemini API 路由处理器
|
||||
// 这些路由将挂载在 /gemini 路径下,处理标准 Gemini API 格式的请求
|
||||
// 标准格式: /gemini/v1beta/models/{model}:generateContent
|
||||
@@ -552,21 +636,38 @@ async function handleStandardStreamGenerateContent(req, res) {
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
const normalizedError = await normalizeAxiosStreamError(error)
|
||||
|
||||
logger.error(`Error in standard streamGenerateContent endpoint`, {
|
||||
message: error.message,
|
||||
status: error.response?.status,
|
||||
statusText: error.response?.statusText,
|
||||
responseData: error.response?.data,
|
||||
responseData: normalizedError.parsedBody || normalizedError.rawBody,
|
||||
stack: error.stack
|
||||
})
|
||||
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({
|
||||
const statusCode = normalizedError.status || 500
|
||||
const responseBody = {
|
||||
error: {
|
||||
message: error.message || 'Internal server error',
|
||||
message: normalizedError.message,
|
||||
type: 'api_error'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (normalizedError.status) {
|
||||
responseBody.error.upstreamStatus = normalizedError.status
|
||||
}
|
||||
if (normalizedError.statusText) {
|
||||
responseBody.error.upstreamStatusText = normalizedError.statusText
|
||||
}
|
||||
if (normalizedError.parsedBody && typeof normalizedError.parsedBody === 'object') {
|
||||
responseBody.error.upstreamResponse = normalizedError.parsedBody
|
||||
} else if (normalizedError.rawBody) {
|
||||
responseBody.error.upstreamRaw = normalizedError.rawBody
|
||||
}
|
||||
|
||||
return res.status(statusCode).json(responseBody)
|
||||
}
|
||||
} finally {
|
||||
// 清理资源
|
||||
|
||||
@@ -1048,6 +1048,7 @@ async function getOauthClient(accessToken, refreshToken, proxyConfig = null) {
|
||||
|
||||
// 验证凭据本地有效性
|
||||
const { token } = await client.getAccessToken()
|
||||
|
||||
if (!token) {
|
||||
return false
|
||||
}
|
||||
@@ -1066,6 +1067,42 @@ async function loadCodeAssist(client, projectId = null, proxyConfig = null) {
|
||||
const CODE_ASSIST_API_VERSION = 'v1internal'
|
||||
|
||||
const { token } = await client.getAccessToken()
|
||||
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.httpsAgent = proxyAgent
|
||||
}
|
||||
|
||||
await axios(tokenInfoConfig)
|
||||
logger.info('📋 tokeninfo 接口验证成功')
|
||||
|
||||
const userInfoConfig = {
|
||||
url: 'https://www.googleapis.com/oauth2/v2/userinfo',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: '*/*'
|
||||
},
|
||||
timeout: 15000
|
||||
}
|
||||
|
||||
if (proxyAgent) {
|
||||
userInfoConfig.httpsAgent = proxyAgent
|
||||
}
|
||||
|
||||
await axios(userInfoConfig)
|
||||
logger.info('📋 userinfo 接口获取成功')
|
||||
|
||||
// 创建ClientMetadata
|
||||
const clientMetadata = {
|
||||
@@ -1100,7 +1137,6 @@ async function loadCodeAssist(client, projectId = null, proxyConfig = null) {
|
||||
}
|
||||
|
||||
// 添加代理配置
|
||||
const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig)
|
||||
if (proxyAgent) {
|
||||
axiosConfig.httpsAgent = proxyAgent
|
||||
logger.info(
|
||||
|
||||
Reference in New Issue
Block a user