mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
feat: 实现 Antigravity OAuth 账户支持与路径分流
This commit is contained in:
@@ -19,6 +19,16 @@ function generateSessionHash(req) {
|
||||
return crypto.createHash('sha256').update(sessionData).digest('hex')
|
||||
}
|
||||
|
||||
function ensureAntigravityProjectId(account) {
|
||||
if (account.projectId) {
|
||||
return account.projectId
|
||||
}
|
||||
if (account.tempProjectId) {
|
||||
return account.tempProjectId
|
||||
}
|
||||
return `ag-${crypto.randomBytes(8).toString('hex')}`
|
||||
}
|
||||
|
||||
// 检查 API Key 权限
|
||||
function checkPermissions(apiKeyData, requiredPermission = 'gemini') {
|
||||
const permissions = apiKeyData.permissions || 'all'
|
||||
@@ -335,25 +345,48 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
|
||||
const client = await geminiAccountService.getOauthClient(
|
||||
account.accessToken,
|
||||
account.refreshToken,
|
||||
proxyConfig
|
||||
proxyConfig,
|
||||
account.oauthProvider
|
||||
)
|
||||
if (actualStream) {
|
||||
// 流式响应
|
||||
const oauthProvider = account.oauthProvider || 'gemini-cli'
|
||||
let { projectId } = account
|
||||
|
||||
if (oauthProvider === 'antigravity') {
|
||||
projectId = ensureAntigravityProjectId(account)
|
||||
if (!account.projectId && account.tempProjectId !== projectId) {
|
||||
await geminiAccountService.updateTempProjectId(account.id, projectId)
|
||||
account.tempProjectId = projectId
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('StreamGenerateContent request', {
|
||||
model,
|
||||
projectId: account.projectId,
|
||||
projectId,
|
||||
apiKeyId: apiKeyData.id
|
||||
})
|
||||
|
||||
const streamResponse = await geminiAccountService.generateContentStream(
|
||||
client,
|
||||
{ model, request: geminiRequestBody },
|
||||
null, // user_prompt_id
|
||||
account.projectId, // 使用有权限的项目ID
|
||||
apiKeyData.id, // 使用 API Key ID 作为 session ID
|
||||
abortController.signal, // 传递中止信号
|
||||
proxyConfig // 传递代理配置
|
||||
)
|
||||
const streamResponse =
|
||||
oauthProvider === 'antigravity'
|
||||
? await geminiAccountService.generateContentStreamAntigravity(
|
||||
client,
|
||||
{ model, request: geminiRequestBody },
|
||||
null, // user_prompt_id
|
||||
projectId,
|
||||
apiKeyData.id, // 使用 API Key ID 作为 session ID
|
||||
abortController.signal, // 传递中止信号
|
||||
proxyConfig // 传递代理配置
|
||||
)
|
||||
: await geminiAccountService.generateContentStream(
|
||||
client,
|
||||
{ model, request: geminiRequestBody },
|
||||
null, // user_prompt_id
|
||||
projectId, // 使用有权限的项目ID
|
||||
apiKeyData.id, // 使用 API Key ID 作为 session ID
|
||||
abortController.signal, // 传递中止信号
|
||||
proxyConfig // 传递代理配置
|
||||
)
|
||||
|
||||
// 设置流式响应头
|
||||
res.setHeader('Content-Type', 'text/event-stream')
|
||||
@@ -559,20 +592,41 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
|
||||
})
|
||||
} else {
|
||||
// 非流式响应
|
||||
const oauthProvider = account.oauthProvider || 'gemini-cli'
|
||||
let { projectId } = account
|
||||
|
||||
if (oauthProvider === 'antigravity') {
|
||||
projectId = ensureAntigravityProjectId(account)
|
||||
if (!account.projectId && account.tempProjectId !== projectId) {
|
||||
await geminiAccountService.updateTempProjectId(account.id, projectId)
|
||||
account.tempProjectId = projectId
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('GenerateContent request', {
|
||||
model,
|
||||
projectId: account.projectId,
|
||||
projectId,
|
||||
apiKeyId: apiKeyData.id
|
||||
})
|
||||
|
||||
const response = await geminiAccountService.generateContent(
|
||||
client,
|
||||
{ model, request: geminiRequestBody },
|
||||
null, // user_prompt_id
|
||||
account.projectId, // 使用有权限的项目ID
|
||||
apiKeyData.id, // 使用 API Key ID 作为 session ID
|
||||
proxyConfig // 传递代理配置
|
||||
)
|
||||
const response =
|
||||
oauthProvider === 'antigravity'
|
||||
? await geminiAccountService.generateContentAntigravity(
|
||||
client,
|
||||
{ model, request: geminiRequestBody },
|
||||
null, // user_prompt_id
|
||||
projectId,
|
||||
apiKeyData.id, // 使用 API Key ID 作为 session ID
|
||||
proxyConfig // 传递代理配置
|
||||
)
|
||||
: await geminiAccountService.generateContent(
|
||||
client,
|
||||
{ model, request: geminiRequestBody },
|
||||
null, // user_prompt_id
|
||||
projectId, // 使用有权限的项目ID
|
||||
apiKeyData.id, // 使用 API Key ID 作为 session ID
|
||||
proxyConfig // 传递代理配置
|
||||
)
|
||||
|
||||
// 转换为 OpenAI 格式并返回
|
||||
const openaiResponse = convertGeminiResponseToOpenAI(response, model, false)
|
||||
@@ -604,7 +658,15 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
|
||||
const duration = Date.now() - startTime
|
||||
logger.info(`OpenAI-Gemini request completed in ${duration}ms`)
|
||||
} catch (error) {
|
||||
logger.error('OpenAI-Gemini request error:', error)
|
||||
const statusForLog = error?.status || error?.response?.status
|
||||
logger.error('OpenAI-Gemini request error', {
|
||||
message: error?.message,
|
||||
status: statusForLog,
|
||||
code: error?.code,
|
||||
requestUrl: error?.config?.url,
|
||||
requestMethod: error?.config?.method,
|
||||
upstreamTraceId: error?.response?.headers?.['x-cloudaicompanion-trace-id']
|
||||
})
|
||||
|
||||
// 处理速率限制
|
||||
if (error.status === 429) {
|
||||
@@ -665,8 +727,21 @@ router.get('/v1/models', authenticateApiKey, async (req, res) => {
|
||||
let models = []
|
||||
|
||||
if (account) {
|
||||
// 获取实际的模型列表
|
||||
models = await getAvailableModels(account.accessToken, account.proxy)
|
||||
// 获取实际的模型列表(失败时回退到默认列表,避免影响 /v1/models 可用性)
|
||||
try {
|
||||
const oauthProvider = account.oauthProvider || 'gemini-cli'
|
||||
models =
|
||||
oauthProvider === 'antigravity'
|
||||
? await geminiAccountService.fetchAvailableModelsAntigravity(
|
||||
account.accessToken,
|
||||
account.proxy,
|
||||
account.refreshToken
|
||||
)
|
||||
: await getAvailableModels(account.accessToken, account.proxy)
|
||||
} catch (error) {
|
||||
logger.warn('Failed to get Gemini models list from upstream, fallback to default:', error)
|
||||
models = []
|
||||
}
|
||||
} else {
|
||||
// 返回默认模型列表
|
||||
models = [
|
||||
@@ -679,6 +754,17 @@ router.get('/v1/models', authenticateApiKey, async (req, res) => {
|
||||
]
|
||||
}
|
||||
|
||||
if (!models || models.length === 0) {
|
||||
models = [
|
||||
{
|
||||
id: 'gemini-2.0-flash-exp',
|
||||
object: 'model',
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
owned_by: 'google'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 如果启用了模型限制,过滤模型列表
|
||||
if (apiKeyData.enableModelRestriction && apiKeyData.restrictedModels.length > 0) {
|
||||
models = models.filter((model) => apiKeyData.restrictedModels.includes(model.id))
|
||||
|
||||
Reference in New Issue
Block a user