mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 21:17:30 +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}`)
|
logger.success(`☁️ Admin created Bedrock account: ${name}`)
|
||||||
const formattedAccount = formatAccountExpiry(formattedAccount)
|
const formattedAccount = formatAccountExpiry(result.data)
|
||||||
return res.json({ success: true, data: result.data })
|
return res.json({ success: true, data: formattedAccount })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Failed to create Bedrock account:', error)
|
logger.error('❌ Failed to create Bedrock account:', error)
|
||||||
return res
|
return res
|
||||||
@@ -4078,8 +4078,8 @@ router.post('/gemini-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.success(`🏢 Admin created new Gemini account: ${accountData.name}`)
|
logger.success(`🏢 Admin created new Gemini account: ${accountData.name}`)
|
||||||
const formattedAccount = formatAccountExpiry(formattedAccount)
|
const formattedAccount = formatAccountExpiry(newAccount)
|
||||||
return res.json({ success: true, data: newAccount })
|
return res.json({ success: true, data: formattedAccount })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Failed to create Gemini account:', error)
|
logger.error('❌ Failed to create Gemini account:', error)
|
||||||
return res.status(500).json({ error: 'Failed to create account', message: error.message })
|
return res.status(500).json({ error: 'Failed to create account', message: error.message })
|
||||||
|
|||||||
@@ -44,6 +44,90 @@ function ensureGeminiPermissionMiddleware(req, res, next) {
|
|||||||
return undefined
|
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 API 路由处理器
|
||||||
// 这些路由将挂载在 /gemini 路径下,处理标准 Gemini API 格式的请求
|
// 这些路由将挂载在 /gemini 路径下,处理标准 Gemini API 格式的请求
|
||||||
// 标准格式: /gemini/v1beta/models/{model}:generateContent
|
// 标准格式: /gemini/v1beta/models/{model}:generateContent
|
||||||
@@ -552,21 +636,38 @@ async function handleStandardStreamGenerateContent(req, res) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
const normalizedError = await normalizeAxiosStreamError(error)
|
||||||
|
|
||||||
logger.error(`Error in standard streamGenerateContent endpoint`, {
|
logger.error(`Error in standard streamGenerateContent endpoint`, {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
status: error.response?.status,
|
status: error.response?.status,
|
||||||
statusText: error.response?.statusText,
|
statusText: error.response?.statusText,
|
||||||
responseData: error.response?.data,
|
responseData: normalizedError.parsedBody || normalizedError.rawBody,
|
||||||
stack: error.stack
|
stack: error.stack
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!res.headersSent) {
|
if (!res.headersSent) {
|
||||||
res.status(500).json({
|
const statusCode = normalizedError.status || 500
|
||||||
|
const responseBody = {
|
||||||
error: {
|
error: {
|
||||||
message: error.message || 'Internal server error',
|
message: normalizedError.message,
|
||||||
type: 'api_error'
|
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 {
|
} finally {
|
||||||
// 清理资源
|
// 清理资源
|
||||||
|
|||||||
@@ -1048,6 +1048,7 @@ async function getOauthClient(accessToken, refreshToken, proxyConfig = null) {
|
|||||||
|
|
||||||
// 验证凭据本地有效性
|
// 验证凭据本地有效性
|
||||||
const { token } = await client.getAccessToken()
|
const { token } = await client.getAccessToken()
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -1066,6 +1067,42 @@ async function loadCodeAssist(client, projectId = null, proxyConfig = null) {
|
|||||||
const CODE_ASSIST_API_VERSION = 'v1internal'
|
const CODE_ASSIST_API_VERSION = 'v1internal'
|
||||||
|
|
||||||
const { token } = await client.getAccessToken()
|
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
|
// 创建ClientMetadata
|
||||||
const clientMetadata = {
|
const clientMetadata = {
|
||||||
@@ -1100,7 +1137,6 @@ async function loadCodeAssist(client, projectId = null, proxyConfig = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加代理配置
|
// 添加代理配置
|
||||||
const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig)
|
|
||||||
if (proxyAgent) {
|
if (proxyAgent) {
|
||||||
axiosConfig.httpsAgent = proxyAgent
|
axiosConfig.httpsAgent = proxyAgent
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
Reference in New Issue
Block a user