fix: 修复gemini-api账户共享池无法调度问题

This commit is contained in:
shaw
2025-11-29 10:02:51 +08:00
parent c6a7771b81
commit 63a7c2514b
2 changed files with 228 additions and 31 deletions

View File

@@ -630,15 +630,22 @@ async function handleModels(req, res) {
}) })
} }
// 选择账户获取模型列表 // 选择账户获取模型列表(允许 API 账户)
let account = null let account = null
let isApiAccount = false
try { try {
const accountSelection = await unifiedGeminiScheduler.selectAccountForApiKey( const accountSelection = await unifiedGeminiScheduler.selectAccountForApiKey(
apiKeyData, apiKeyData,
null, null,
null null,
{ allowApiAccounts: true }
) )
isApiAccount = accountSelection.accountType === 'gemini-api'
if (isApiAccount) {
account = await geminiApiAccountService.getAccount(accountSelection.accountId)
} else {
account = await geminiAccountService.getAccount(accountSelection.accountId) account = await geminiAccountService.getAccount(accountSelection.accountId)
}
} catch (error) { } catch (error) {
logger.warn('Failed to select Gemini account for models endpoint:', error) logger.warn('Failed to select Gemini account for models endpoint:', error)
} }
@@ -659,7 +666,45 @@ async function handleModels(req, res) {
} }
// 获取模型列表 // 获取模型列表
const models = await getAvailableModels(account.accessToken, account.proxy) let models
if (isApiAccount) {
// API Key 账户:使用 API Key 获取模型列表
const proxyConfig = parseProxyConfig(account)
try {
const apiUrl = `${account.baseUrl}/v1beta/models?key=${account.apiKey}`
const axiosConfig = {
method: 'GET',
url: apiUrl,
headers: { 'Content-Type': 'application/json' }
}
if (proxyConfig) {
const proxyHelper = new ProxyHelper()
axiosConfig.httpsAgent = proxyHelper.createProxyAgent(proxyConfig)
axiosConfig.httpAgent = proxyHelper.createProxyAgent(proxyConfig)
}
const response = await axios(axiosConfig)
models = (response.data.models || []).map((m) => ({
id: m.name?.replace('models/', '') || m.name,
object: 'model',
created: Date.now() / 1000,
owned_by: 'google'
}))
} catch (error) {
logger.warn('Failed to fetch models from Gemini API:', error.message)
// 返回默认模型列表
models = [
{
id: 'gemini-2.5-flash',
object: 'model',
created: Date.now() / 1000,
owned_by: 'google'
}
]
}
} else {
// OAuth 账户:使用 OAuth token 获取模型列表
models = await getAvailableModels(account.accessToken, account.proxy)
}
res.json({ res.json({
object: 'list', object: 'list',
@@ -786,12 +831,36 @@ function handleSimpleEndpoint(apiMethod) {
// 从路径参数或请求体中获取模型名 // 从路径参数或请求体中获取模型名
const requestedModel = req.body.model || req.params.modelName || 'gemini-2.5-flash' const requestedModel = req.body.model || req.params.modelName || 'gemini-2.5-flash'
const { accountId } = await unifiedGeminiScheduler.selectAccountForApiKey( const schedulerResult = await unifiedGeminiScheduler.selectAccountForApiKey(
req.apiKey, req.apiKey,
sessionHash, sessionHash,
requestedModel requestedModel
) )
const { accountId, accountType } = schedulerResult
// v1internal 路由只支持 OAuth 账户,不支持 API Key 账户
if (accountType === 'gemini-api') {
logger.error(
`❌ v1internal routes do not support Gemini API accounts. Account: ${accountId}`
)
return res.status(400).json({
error: {
message:
'This endpoint only supports Gemini OAuth accounts. Gemini API Key accounts are not compatible with v1internal format.',
type: 'invalid_account_type'
}
})
}
const account = await geminiAccountService.getAccount(accountId) const account = await geminiAccountService.getAccount(accountId)
if (!account) {
return res.status(404).json({
error: {
message: 'Gemini account not found',
type: 'account_not_found'
}
})
}
const { accessToken, refreshToken } = account const { accessToken, refreshToken } = account
const version = req.path.includes('v1beta') ? 'v1beta' : 'v1internal' const version = req.path.includes('v1beta') ? 'v1beta' : 'v1internal'
@@ -842,12 +911,34 @@ async function handleLoadCodeAssist(req, res) {
// 从路径参数或请求体中获取模型名 // 从路径参数或请求体中获取模型名
const requestedModel = req.body.model || req.params.modelName || 'gemini-2.5-flash' const requestedModel = req.body.model || req.params.modelName || 'gemini-2.5-flash'
const { accountId } = await unifiedGeminiScheduler.selectAccountForApiKey( const schedulerResult = await unifiedGeminiScheduler.selectAccountForApiKey(
req.apiKey, req.apiKey,
sessionHash, sessionHash,
requestedModel requestedModel
) )
const { accountId, accountType } = schedulerResult
// v1internal 路由只支持 OAuth 账户,不支持 API Key 账户
if (accountType === 'gemini-api') {
logger.error(`❌ v1internal routes do not support Gemini API accounts. Account: ${accountId}`)
return res.status(400).json({
error: {
message:
'This endpoint only supports Gemini OAuth accounts. Gemini API Key accounts are not compatible with v1internal format.',
type: 'invalid_account_type'
}
})
}
const account = await geminiAccountService.getAccount(accountId) const account = await geminiAccountService.getAccount(accountId)
if (!account) {
return res.status(404).json({
error: {
message: 'Gemini account not found',
type: 'account_not_found'
}
})
}
const { accessToken, refreshToken, projectId } = account const { accessToken, refreshToken, projectId } = account
const { metadata, cloudaicompanionProject } = req.body const { metadata, cloudaicompanionProject } = req.body
@@ -919,12 +1010,34 @@ async function handleOnboardUser(req, res) {
// 从路径参数或请求体中获取模型名 // 从路径参数或请求体中获取模型名
const requestedModel = req.body.model || req.params.modelName || 'gemini-2.5-flash' const requestedModel = req.body.model || req.params.modelName || 'gemini-2.5-flash'
const { accountId } = await unifiedGeminiScheduler.selectAccountForApiKey( const schedulerResult = await unifiedGeminiScheduler.selectAccountForApiKey(
req.apiKey, req.apiKey,
sessionHash, sessionHash,
requestedModel requestedModel
) )
const { accountId, accountType } = schedulerResult
// v1internal 路由只支持 OAuth 账户,不支持 API Key 账户
if (accountType === 'gemini-api') {
logger.error(`❌ v1internal routes do not support Gemini API accounts. Account: ${accountId}`)
return res.status(400).json({
error: {
message:
'This endpoint only supports Gemini OAuth accounts. Gemini API Key accounts are not compatible with v1internal format.',
type: 'invalid_account_type'
}
})
}
const account = await geminiAccountService.getAccount(accountId) const account = await geminiAccountService.getAccount(accountId)
if (!account) {
return res.status(404).json({
error: {
message: 'Gemini account not found',
type: 'account_not_found'
}
})
}
const { accessToken, refreshToken, projectId } = account const { accessToken, refreshToken, projectId } = account
const version = req.path.includes('v1beta') ? 'v1beta' : 'v1internal' const version = req.path.includes('v1beta') ? 'v1beta' : 'v1internal'
@@ -1013,31 +1126,93 @@ async function handleCountTokens(req, res) {
}) })
} }
// 使用统一调度选择账号 // 使用统一调度选择账号(允许 API 账户)
const { accountId } = await unifiedGeminiScheduler.selectAccountForApiKey( const schedulerResult = await unifiedGeminiScheduler.selectAccountForApiKey(
req.apiKey, req.apiKey,
sessionHash, sessionHash,
model model,
{ allowApiAccounts: true }
) )
const account = await geminiAccountService.getAccount(accountId) const { accountId, accountType } = schedulerResult
const { accessToken, refreshToken } = account const isApiAccount = accountType === 'gemini-api'
const version = req.path.includes('v1beta') ? 'v1beta' : 'v1internal' let account
logger.info(`CountTokens request (${version})`, { if (isApiAccount) {
account = await geminiApiAccountService.getAccount(accountId)
} else {
account = await geminiAccountService.getAccount(accountId)
}
if (!account) {
return res.status(404).json({
error: {
message: `${isApiAccount ? 'Gemini API' : 'Gemini'} account not found`,
type: 'account_not_found'
}
})
}
const version = req.path.includes('v1beta') ? 'v1beta' : 'v1'
logger.info(
`CountTokens request (${version}) - ${isApiAccount ? 'API Key' : 'OAuth'} Account`,
{
model, model,
contentsLength: contents.length, contentsLength: contents.length,
accountId,
apiKeyId: req.apiKey?.id || 'unknown' apiKeyId: req.apiKey?.id || 'unknown'
}) }
)
// 解析账户的代理配置 // 解析账户的代理配置
const proxyConfig = parseProxyConfig(account) const proxyConfig = parseProxyConfig(account)
const client = await geminiAccountService.getOauthClient(accessToken, refreshToken, proxyConfig) let response
const response = await geminiAccountService.countTokens(client, contents, model, proxyConfig) if (isApiAccount) {
// API Key 账户:直接使用 API Key 请求
const modelPath = model.startsWith('models/') ? model : `models/${model}`
const apiUrl = `${account.baseUrl}/v1beta/${modelPath}:countTokens?key=${account.apiKey}`
const axiosConfig = {
method: 'POST',
url: apiUrl,
data: { contents },
headers: { 'Content-Type': 'application/json' }
}
if (proxyConfig) {
const proxyHelper = new ProxyHelper()
axiosConfig.httpsAgent = proxyHelper.createProxyAgent(proxyConfig)
axiosConfig.httpAgent = proxyHelper.createProxyAgent(proxyConfig)
}
try {
const apiResponse = await axios(axiosConfig)
response = {
totalTokens: apiResponse.data.totalTokens || 0,
totalBillableCharacters: apiResponse.data.totalBillableCharacters || 0,
...apiResponse.data
}
} catch (error) {
logger.error('Gemini API countTokens request failed:', {
status: error.response?.status,
data: error.response?.data
})
throw error
}
} else {
// OAuth 账户
const { accessToken, refreshToken } = account
const client = await geminiAccountService.getOauthClient(
accessToken,
refreshToken,
proxyConfig
)
response = await geminiAccountService.countTokens(client, contents, model, proxyConfig)
}
res.json(response) res.json(response)
} catch (error) { } catch (error) {
const version = req.path.includes('v1beta') ? 'v1beta' : 'v1internal' const version = req.path.includes('v1beta') ? 'v1beta' : 'v1'
logger.error(`Error in countTokens endpoint (${version})`, { error: error.message }) logger.error(`Error in countTokens endpoint (${version})`, { error: error.message })
res.status(500).json({ res.status(500).json({
error: { error: {

View File

@@ -19,6 +19,12 @@ class UnifiedGeminiScheduler {
return schedulable !== false && schedulable !== 'false' return schedulable !== false && schedulable !== 'false'
} }
// 🔧 辅助方法:检查账户是否激活(兼容字符串和布尔值)
_isActive(isActive) {
// 兼容布尔值 true 和字符串 'true'
return isActive === true || isActive === 'true'
}
// 🎯 统一调度Gemini账号 // 🎯 统一调度Gemini账号
async selectAccountForApiKey( async selectAccountForApiKey(
apiKeyData, apiKeyData,
@@ -35,7 +41,11 @@ class UnifiedGeminiScheduler {
if (apiKeyData.geminiAccountId.startsWith('api:')) { if (apiKeyData.geminiAccountId.startsWith('api:')) {
const accountId = apiKeyData.geminiAccountId.replace('api:', '') const accountId = apiKeyData.geminiAccountId.replace('api:', '')
const boundAccount = await geminiApiAccountService.getAccount(accountId) const boundAccount = await geminiApiAccountService.getAccount(accountId)
if (boundAccount && boundAccount.isActive === 'true' && boundAccount.status !== 'error') { if (
boundAccount &&
this._isActive(boundAccount.isActive) &&
boundAccount.status !== 'error'
) {
logger.info( logger.info(
`🎯 Using bound Gemini-API account: ${boundAccount.name} (${accountId}) for API key ${apiKeyData.name}` `🎯 Using bound Gemini-API account: ${boundAccount.name} (${accountId}) for API key ${apiKeyData.name}`
) )
@@ -68,7 +78,11 @@ class UnifiedGeminiScheduler {
// 普通 Gemini OAuth 专属账户 // 普通 Gemini OAuth 专属账户
else { else {
const boundAccount = await geminiAccountService.getAccount(apiKeyData.geminiAccountId) const boundAccount = await geminiAccountService.getAccount(apiKeyData.geminiAccountId)
if (boundAccount && boundAccount.isActive === 'true' && boundAccount.status !== 'error') { if (
boundAccount &&
this._isActive(boundAccount.isActive) &&
boundAccount.status !== 'error'
) {
logger.info( logger.info(
`🎯 Using bound dedicated Gemini account: ${boundAccount.name} (${apiKeyData.geminiAccountId}) for API key ${apiKeyData.name}` `🎯 Using bound dedicated Gemini account: ${boundAccount.name} (${apiKeyData.geminiAccountId}) for API key ${apiKeyData.name}`
) )
@@ -184,7 +198,11 @@ class UnifiedGeminiScheduler {
if (apiKeyData.geminiAccountId.startsWith('api:')) { if (apiKeyData.geminiAccountId.startsWith('api:')) {
const accountId = apiKeyData.geminiAccountId.replace('api:', '') const accountId = apiKeyData.geminiAccountId.replace('api:', '')
const boundAccount = await geminiApiAccountService.getAccount(accountId) const boundAccount = await geminiApiAccountService.getAccount(accountId)
if (boundAccount && boundAccount.isActive === 'true' && boundAccount.status !== 'error') { if (
boundAccount &&
this._isActive(boundAccount.isActive) &&
boundAccount.status !== 'error'
) {
const isRateLimited = await this.isAccountRateLimited(accountId) const isRateLimited = await this.isAccountRateLimited(accountId)
if (!isRateLimited) { if (!isRateLimited) {
// 检查模型支持 // 检查模型支持
@@ -231,7 +249,11 @@ class UnifiedGeminiScheduler {
// 普通 Gemini OAuth 账户 // 普通 Gemini OAuth 账户
else if (!apiKeyData.geminiAccountId.startsWith('group:')) { else if (!apiKeyData.geminiAccountId.startsWith('group:')) {
const boundAccount = await geminiAccountService.getAccount(apiKeyData.geminiAccountId) const boundAccount = await geminiAccountService.getAccount(apiKeyData.geminiAccountId)
if (boundAccount && boundAccount.isActive === 'true' && boundAccount.status !== 'error') { if (
boundAccount &&
this._isActive(boundAccount.isActive) &&
boundAccount.status !== 'error'
) {
const isRateLimited = await this.isAccountRateLimited(boundAccount.id) const isRateLimited = await this.isAccountRateLimited(boundAccount.id)
if (!isRateLimited) { if (!isRateLimited) {
// 检查模型支持 // 检查模型支持
@@ -276,7 +298,7 @@ class UnifiedGeminiScheduler {
const geminiAccounts = await geminiAccountService.getAllAccounts() const geminiAccounts = await geminiAccountService.getAllAccounts()
for (const account of geminiAccounts) { for (const account of geminiAccounts) {
if ( if (
account.isActive === 'true' && this._isActive(account.isActive) &&
account.status !== 'error' && account.status !== 'error' &&
(account.accountType === 'shared' || !account.accountType) && // 兼容旧数据 (account.accountType === 'shared' || !account.accountType) && // 兼容旧数据
this._isSchedulable(account.schedulable) this._isSchedulable(account.schedulable)
@@ -326,7 +348,7 @@ class UnifiedGeminiScheduler {
const geminiApiAccounts = await geminiApiAccountService.getAllAccounts() const geminiApiAccounts = await geminiApiAccountService.getAllAccounts()
for (const account of geminiApiAccounts) { for (const account of geminiApiAccounts) {
if ( if (
account.isActive === 'true' && this._isActive(account.isActive) &&
account.status !== 'error' && account.status !== 'error' &&
(account.accountType === 'shared' || !account.accountType) && (account.accountType === 'shared' || !account.accountType) &&
this._isSchedulable(account.schedulable) this._isSchedulable(account.schedulable)
@@ -386,7 +408,7 @@ class UnifiedGeminiScheduler {
try { try {
if (accountType === 'gemini') { if (accountType === 'gemini') {
const account = await geminiAccountService.getAccount(accountId) const account = await geminiAccountService.getAccount(accountId)
if (!account || account.isActive !== 'true' || account.status === 'error') { if (!account || !this._isActive(account.isActive) || account.status === 'error') {
return false return false
} }
// 检查是否可调度 // 检查是否可调度
@@ -397,7 +419,7 @@ class UnifiedGeminiScheduler {
return !(await this.isAccountRateLimited(accountId)) return !(await this.isAccountRateLimited(accountId))
} else if (accountType === 'gemini-api') { } else if (accountType === 'gemini-api') {
const account = await geminiApiAccountService.getAccount(accountId) const account = await geminiApiAccountService.getAccount(accountId)
if (!account || account.isActive !== 'true' || account.status === 'error') { if (!account || !this._isActive(account.isActive) || account.status === 'error') {
return false return false
} }
// 检查是否可调度 // 检查是否可调度
@@ -643,7 +665,7 @@ class UnifiedGeminiScheduler {
// 检查账户是否可用 // 检查账户是否可用
if ( if (
account.isActive === 'true' && this._isActive(account.isActive) &&
account.status !== 'error' && account.status !== 'error' &&
this._isSchedulable(account.schedulable) this._isSchedulable(account.schedulable)
) { ) {