refactor: standardize code formatting and linting configuration

- Replace .eslintrc.js with .eslintrc.cjs for better ES module compatibility
- Add .prettierrc configuration for consistent code formatting
- Update package.json with new lint and format scripts
- Add nodemon.json for development hot reloading configuration
- Standardize code formatting across all JavaScript and Vue files
- Update web admin SPA with improved linting rules and formatting
- Add prettier configuration to web admin SPA

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
千羽
2025-08-07 18:19:31 +09:00
parent 4a0eba117c
commit 8a74bf5afe
124 changed files with 20878 additions and 18757 deletions

View File

@@ -1,11 +1,11 @@
const express = require('express');
const router = express.Router();
const logger = require('../utils/logger');
const { authenticateApiKey } = require('../middleware/auth');
const geminiAccountService = require('../services/geminiAccountService');
const unifiedGeminiScheduler = require('../services/unifiedGeminiScheduler');
const { getAvailableModels } = require('../services/geminiRelayService');
const crypto = require('crypto');
const express = require('express')
const router = express.Router()
const logger = require('../utils/logger')
const { authenticateApiKey } = require('../middleware/auth')
const geminiAccountService = require('../services/geminiAccountService')
const unifiedGeminiScheduler = require('../services/unifiedGeminiScheduler')
const { getAvailableModels } = require('../services/geminiRelayService')
const crypto = require('crypto')
// 生成会话哈希
function generateSessionHash(req) {
@@ -13,167 +13,182 @@ function generateSessionHash(req) {
req.headers['user-agent'],
req.ip,
req.headers['authorization']?.substring(0, 20)
].filter(Boolean).join(':');
return crypto.createHash('sha256').update(sessionData).digest('hex');
]
.filter(Boolean)
.join(':')
return crypto.createHash('sha256').update(sessionData).digest('hex')
}
// 检查 API Key 权限
function checkPermissions(apiKeyData, requiredPermission = 'gemini') {
const permissions = apiKeyData.permissions || 'all';
return permissions === 'all' || permissions === requiredPermission;
const permissions = apiKeyData.permissions || 'all'
return permissions === 'all' || permissions === requiredPermission
}
// 转换 OpenAI 消息格式到 Gemini 格式
function convertMessagesToGemini(messages) {
const contents = [];
let systemInstruction = '';
const contents = []
let systemInstruction = ''
// 辅助函数:提取文本内容
function extractTextContent(content) {
// 处理 null 或 undefined
if (content == null) {
return '';
if (content === null || content === undefined) {
return ''
}
// 处理字符串
if (typeof content === 'string') {
return content;
return content
}
// 处理数组格式的内容
if (Array.isArray(content)) {
return content.map(item => {
if (item == null) return '';
if (typeof item === 'string') {
return item;
}
if (typeof item === 'object') {
// 处理 {type: 'text', text: '...'} 格式
if (item.type === 'text' && item.text) {
return item.text;
return content
.map((item) => {
if (item === null || item === undefined) {
return ''
}
// 处理 {text: '...'} 格式
if (item.text) {
return item.text;
if (typeof item === 'string') {
return item
}
// 处理嵌套的对象或数组
if (item.content) {
return extractTextContent(item.content);
if (typeof item === 'object') {
// 处理 {type: 'text', text: '...'} 格式
if (item.type === 'text' && item.text) {
return item.text
}
// 处理 {text: '...'} 格式
if (item.text) {
return item.text
}
// 处理嵌套的对象或数组
if (item.content) {
return extractTextContent(item.content)
}
}
}
return '';
}).join('');
return ''
})
.join('')
}
// 处理对象格式的内容
if (typeof content === 'object') {
// 处理 {text: '...'} 格式
if (content.text) {
return content.text;
return content.text
}
// 处理 {content: '...'} 格式
if (content.content) {
return extractTextContent(content.content);
return extractTextContent(content.content)
}
// 处理 {parts: [{text: '...'}]} 格式
if (content.parts && Array.isArray(content.parts)) {
return content.parts.map(part => {
if (part && part.text) {
return part.text;
}
return '';
}).join('');
return content.parts
.map((part) => {
if (part && part.text) {
return part.text
}
return ''
})
.join('')
}
}
// 最后的后备选项:只有在内容确实不为空且有意义时才转换为字符串
if (content !== undefined && content !== null && content !== '' && typeof content !== 'object') {
return String(content);
if (
content !== undefined &&
content !== null &&
content !== '' &&
typeof content !== 'object'
) {
return String(content)
}
return '';
return ''
}
for (const message of messages) {
const textContent = extractTextContent(message.content);
const textContent = extractTextContent(message.content)
if (message.role === 'system') {
systemInstruction += (systemInstruction ? '\n\n' : '') + textContent;
systemInstruction += (systemInstruction ? '\n\n' : '') + textContent
} else if (message.role === 'user') {
contents.push({
role: 'user',
parts: [{ text: textContent }]
});
})
} else if (message.role === 'assistant') {
contents.push({
role: 'model',
parts: [{ text: textContent }]
});
})
}
}
return { contents, systemInstruction };
return { contents, systemInstruction }
}
// 转换 Gemini 响应到 OpenAI 格式
function convertGeminiResponseToOpenAI(geminiResponse, model, stream = false) {
if (stream) {
// 处理流式响应 - 原样返回 SSE 数据
return geminiResponse;
return geminiResponse
} else {
// 非流式响应转换
// 处理嵌套的 response 结构
const actualResponse = geminiResponse.response || geminiResponse;
const actualResponse = geminiResponse.response || geminiResponse
if (actualResponse.candidates && actualResponse.candidates.length > 0) {
const candidate = actualResponse.candidates[0];
const content = candidate.content?.parts?.[0]?.text || '';
const finishReason = candidate.finishReason?.toLowerCase() || 'stop';
const candidate = actualResponse.candidates[0]
const content = candidate.content?.parts?.[0]?.text || ''
const finishReason = candidate.finishReason?.toLowerCase() || 'stop'
// 计算 token 使用量
const usage = actualResponse.usageMetadata || {
promptTokenCount: 0,
candidatesTokenCount: 0,
totalTokenCount: 0
};
}
return {
id: `chatcmpl-${Date.now()}`,
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model: model,
choices: [{
index: 0,
message: {
role: 'assistant',
content: content
},
finish_reason: finishReason
}],
model,
choices: [
{
index: 0,
message: {
role: 'assistant',
content
},
finish_reason: finishReason
}
],
usage: {
prompt_tokens: usage.promptTokenCount,
completion_tokens: usage.candidatesTokenCount,
total_tokens: usage.totalTokenCount
}
};
}
} else {
throw new Error('No response from Gemini');
throw new Error('No response from Gemini')
}
}
}
// OpenAI 兼容的聊天完成端点
router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
const startTime = Date.now();
let abortController = null;
let account = null; // Declare account outside try block for error handling
let accountSelection = null; // Declare accountSelection for error handling
let sessionHash = null; // Declare sessionHash for error handling
const startTime = Date.now()
let abortController = null
let account = null // Declare account outside try block for error handling
let accountSelection = null // Declare accountSelection for error handling
let sessionHash = null // Declare sessionHash for error handling
try {
const apiKeyData = req.apiKey;
const apiKeyData = req.apiKey
// 检查权限
if (!checkPermissions(apiKeyData, 'gemini')) {
return res.status(403).json({
@@ -182,25 +197,25 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
type: 'permission_denied',
code: 'permission_denied'
}
});
})
}
// 处理请求体结构 - 支持多种格式
let requestBody = req.body;
let requestBody = req.body
// 如果请求体被包装在 body 字段中,解包它
if (req.body.body && typeof req.body.body === 'object') {
requestBody = req.body.body;
requestBody = req.body.body
}
// 从 URL 路径中提取模型信息(如果存在)
let urlModel = null;
const urlPath = req.body?.config?.url || req.originalUrl || req.url;
const modelMatch = urlPath.match(/\/([^/]+):(?:stream)?[Gg]enerateContent/);
let urlModel = null
const urlPath = req.body?.config?.url || req.originalUrl || req.url
const modelMatch = urlPath.match(/\/([^/]+):(?:stream)?[Gg]enerateContent/)
if (modelMatch) {
urlModel = modelMatch[1];
logger.debug(`Extracted model from URL: ${urlModel}`);
urlModel = modelMatch[1]
logger.debug(`Extracted model from URL: ${urlModel}`)
}
// 提取请求参数
const {
messages: requestMessages,
@@ -209,19 +224,19 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
temperature = 0.7,
max_tokens = 4096,
stream = false
} = requestBody;
} = requestBody
// 检查URL中是否包含stream标识
const isStreamFromUrl = urlPath && urlPath.includes('streamGenerateContent');
const actualStream = stream || isStreamFromUrl;
const isStreamFromUrl = urlPath && urlPath.includes('streamGenerateContent')
const actualStream = stream || isStreamFromUrl
// 优先使用 URL 中的模型,其次是请求体中的模型
const model = urlModel || bodyModel;
const model = urlModel || bodyModel
// 支持两种格式: OpenAI 的 messages 或 Gemini 的 contents
let messages = requestMessages;
let messages = requestMessages
if (requestContents && Array.isArray(requestContents)) {
messages = requestContents;
messages = requestContents
}
// 验证必需参数
@@ -232,9 +247,9 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
type: 'invalid_request_error',
code: 'invalid_request'
}
});
})
}
// 检查模型限制
if (apiKeyData.enableModelRestriction && apiKeyData.restrictedModels.length > 0) {
if (!apiKeyData.restrictedModels.includes(model)) {
@@ -244,13 +259,13 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
type: 'invalid_request_error',
code: 'model_not_allowed'
}
});
})
}
}
// 转换消息格式
const { contents: geminiContents, systemInstruction } = convertMessagesToGemini(messages);
const { contents: geminiContents, systemInstruction } = convertMessagesToGemini(messages)
// 构建 Gemini 请求体
const geminiRequestBody = {
contents: geminiContents,
@@ -259,24 +274,28 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
maxOutputTokens: max_tokens,
candidateCount: 1
}
};
if (systemInstruction) {
geminiRequestBody.systemInstruction = { parts: [{ text: systemInstruction }] };
}
if (systemInstruction) {
geminiRequestBody.systemInstruction = { parts: [{ text: systemInstruction }] }
}
// 生成会话哈希用于粘性会话
sessionHash = generateSessionHash(req);
sessionHash = generateSessionHash(req)
// 选择可用的 Gemini 账户
try {
accountSelection = await unifiedGeminiScheduler.selectAccountForApiKey(apiKeyData, sessionHash, model);
account = await geminiAccountService.getAccount(accountSelection.accountId);
accountSelection = await unifiedGeminiScheduler.selectAccountForApiKey(
apiKeyData,
sessionHash,
model
)
account = await geminiAccountService.getAccount(accountSelection.accountId)
} catch (error) {
logger.error('Failed to select Gemini account:', error);
account = null;
logger.error('Failed to select Gemini account:', error)
account = null
}
if (!account) {
return res.status(503).json({
error: {
@@ -284,35 +303,38 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
type: 'service_unavailable',
code: 'service_unavailable'
}
});
})
}
logger.info(`Using Gemini account: ${account.id} for API key: ${apiKeyData.id}`);
logger.info(`Using Gemini account: ${account.id} for API key: ${apiKeyData.id}`)
// 标记账户被使用
await geminiAccountService.markAccountUsed(account.id);
await geminiAccountService.markAccountUsed(account.id)
// 创建中止控制器
abortController = new AbortController();
abortController = new AbortController()
// 处理客户端断开连接
req.on('close', () => {
if (abortController && !abortController.signal.aborted) {
logger.info('Client disconnected, aborting Gemini request');
abortController.abort();
logger.info('Client disconnected, aborting Gemini request')
abortController.abort()
}
});
})
// 获取OAuth客户端
const client = await geminiAccountService.getOauthClient(account.accessToken, account.refreshToken);
const client = await geminiAccountService.getOauthClient(
account.accessToken,
account.refreshToken
)
if (actualStream) {
// 流式响应
logger.info('StreamGenerateContent request', {
model: model,
model,
projectId: account.projectId,
apiKeyId: apiKeyData.id
});
})
const streamResponse = await geminiAccountService.generateContentStream(
client,
{ model, request: geminiRequestBody },
@@ -320,93 +342,101 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
account.projectId, // 使用有权限的项目ID
apiKeyData.id, // 使用 API Key ID 作为 session ID
abortController.signal // 传递中止信号
);
)
// 设置流式响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('X-Accel-Buffering', 'no');
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Connection', 'keep-alive')
res.setHeader('X-Accel-Buffering', 'no')
// 处理流式响应,转换为 OpenAI 格式
let buffer = '';
let buffer = ''
// 发送初始的空消息,符合 OpenAI 流式格式
const initialChunk = {
id: `chatcmpl-${Date.now()}`,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: model,
choices: [{
index: 0,
delta: { role: 'assistant' },
finish_reason: null
}]
};
res.write(`data: ${JSON.stringify(initialChunk)}\n\n`);
model,
choices: [
{
index: 0,
delta: { role: 'assistant' },
finish_reason: null
}
]
}
res.write(`data: ${JSON.stringify(initialChunk)}\n\n`)
// 用于收集usage数据
let totalUsage = {
promptTokenCount: 0,
candidatesTokenCount: 0,
totalTokenCount: 0
};
let usageReported = false;
}
const usageReported = false
streamResponse.on('data', (chunk) => {
try {
const chunkStr = chunk.toString();
const chunkStr = chunk.toString()
if (!chunkStr.trim()) {
return;
return
}
buffer += chunkStr;
const lines = buffer.split('\n');
buffer = lines.pop() || ''; // 保留最后一个不完整的行
buffer += chunkStr
const lines = buffer.split('\n')
buffer = lines.pop() || '' // 保留最后一个不完整的行
for (const line of lines) {
if (!line.trim()) continue;
// 处理 SSE 格式
let jsonData = line;
if (line.startsWith('data: ')) {
jsonData = line.substring(6).trim();
if (!line.trim()) {
continue
}
if (!jsonData || jsonData === '[DONE]') continue;
// 处理 SSE 格式
let jsonData = line
if (line.startsWith('data: ')) {
jsonData = line.substring(6).trim()
}
if (!jsonData || jsonData === '[DONE]') {
continue
}
try {
const data = JSON.parse(jsonData);
const data = JSON.parse(jsonData)
// 捕获usage数据
if (data.response?.usageMetadata) {
totalUsage = data.response.usageMetadata;
logger.debug('📊 Captured Gemini usage data:', totalUsage);
totalUsage = data.response.usageMetadata
logger.debug('📊 Captured Gemini usage data:', totalUsage)
}
// 转换为 OpenAI 流式格式
if (data.response?.candidates && data.response.candidates.length > 0) {
const candidate = data.response.candidates[0];
const content = candidate.content?.parts?.[0]?.text || '';
const finishReason = candidate.finishReason;
const candidate = data.response.candidates[0]
const content = candidate.content?.parts?.[0]?.text || ''
const { finishReason } = candidate
// 只有当有内容或者是结束标记时才发送数据
if (content || finishReason === 'STOP') {
const openaiChunk = {
id: `chatcmpl-${Date.now()}`,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: model,
choices: [{
index: 0,
delta: content ? { content: content } : {},
finish_reason: finishReason === 'STOP' ? 'stop' : null
}]
};
res.write(`data: ${JSON.stringify(openaiChunk)}\n\n`);
model,
choices: [
{
index: 0,
delta: content ? { content } : {},
finish_reason: finishReason === 'STOP' ? 'stop' : null
}
]
}
res.write(`data: ${JSON.stringify(openaiChunk)}\n\n`)
// 如果结束了,添加 usage 信息并发送最终的 [DONE]
if (finishReason === 'STOP') {
// 如果有 usage 数据,添加到最后一个 chunk
@@ -415,48 +445,50 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
id: `chatcmpl-${Date.now()}`,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: model,
choices: [{
index: 0,
delta: {},
finish_reason: 'stop'
}],
model,
choices: [
{
index: 0,
delta: {},
finish_reason: 'stop'
}
],
usage: {
prompt_tokens: data.response.usageMetadata.promptTokenCount || 0,
completion_tokens: data.response.usageMetadata.candidatesTokenCount || 0,
total_tokens: data.response.usageMetadata.totalTokenCount || 0
}
};
res.write(`data: ${JSON.stringify(usageChunk)}\n\n`);
}
res.write(`data: ${JSON.stringify(usageChunk)}\n\n`)
}
res.write('data: [DONE]\n\n');
res.write('data: [DONE]\n\n')
}
}
}
} catch (e) {
logger.debug('Error parsing JSON line:', e.message);
logger.debug('Error parsing JSON line:', e.message)
}
}
} catch (error) {
logger.error('Stream processing error:', error);
logger.error('Stream processing error:', error)
if (!res.headersSent) {
res.status(500).json({
error: {
message: error.message || 'Stream error',
type: 'api_error'
}
});
})
}
}
});
})
streamResponse.on('end', async () => {
logger.info('Stream completed successfully');
logger.info('Stream completed successfully')
// 记录使用统计
if (!usageReported && totalUsage.totalTokenCount > 0) {
try {
const apiKeyService = require('../services/apiKeyService');
const apiKeyService = require('../services/apiKeyService')
await apiKeyService.recordUsage(
apiKeyData.id,
totalUsage.promptTokenCount || 0,
@@ -465,59 +497,60 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
0, // cacheReadTokens
model,
account.id
);
logger.info(`📊 Recorded Gemini stream usage - Input: ${totalUsage.promptTokenCount}, Output: ${totalUsage.candidatesTokenCount}, Total: ${totalUsage.totalTokenCount}`);
)
logger.info(
`📊 Recorded Gemini stream usage - Input: ${totalUsage.promptTokenCount}, Output: ${totalUsage.candidatesTokenCount}, Total: ${totalUsage.totalTokenCount}`
)
} catch (error) {
logger.error('Failed to record Gemini usage:', error);
logger.error('Failed to record Gemini usage:', error)
}
}
if (!res.headersSent) {
res.write('data: [DONE]\n\n');
res.write('data: [DONE]\n\n')
}
res.end();
});
res.end()
})
streamResponse.on('error', (error) => {
logger.error('Stream error:', error);
logger.error('Stream error:', error)
if (!res.headersSent) {
res.status(500).json({
error: {
message: error.message || 'Stream error',
type: 'api_error'
}
});
})
} else {
// 如果已经开始发送流数据,发送错误事件
res.write(`data: {"error": {"message": "${error.message || 'Stream error'}"}}\n\n`);
res.write('data: [DONE]\n\n');
res.end();
res.write(`data: {"error": {"message": "${error.message || 'Stream error'}"}}\n\n`)
res.write('data: [DONE]\n\n')
res.end()
}
});
})
} else {
// 非流式响应
logger.info('GenerateContent request', {
model: model,
model,
projectId: account.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
);
)
// 转换为 OpenAI 格式并返回
const openaiResponse = convertGeminiResponseToOpenAI(response, model, false);
const openaiResponse = convertGeminiResponseToOpenAI(response, model, false)
// 记录使用统计
if (openaiResponse.usage) {
try {
const apiKeyService = require('../services/apiKeyService');
const apiKeyService = require('../services/apiKeyService')
await apiKeyService.recordUsage(
apiKeyData.id,
openaiResponse.usage.prompt_tokens || 0,
@@ -526,53 +559,55 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
0, // cacheReadTokens
model,
account.id
);
logger.info(`📊 Recorded Gemini usage - Input: ${openaiResponse.usage.prompt_tokens}, Output: ${openaiResponse.usage.completion_tokens}, Total: ${openaiResponse.usage.total_tokens}`);
)
logger.info(
`📊 Recorded Gemini usage - Input: ${openaiResponse.usage.prompt_tokens}, Output: ${openaiResponse.usage.completion_tokens}, Total: ${openaiResponse.usage.total_tokens}`
)
} catch (error) {
logger.error('Failed to record Gemini usage:', error);
logger.error('Failed to record Gemini usage:', error)
}
}
res.json(openaiResponse);
res.json(openaiResponse)
}
const duration = Date.now() - startTime;
logger.info(`OpenAI-Gemini request completed in ${duration}ms`);
const duration = Date.now() - startTime
logger.info(`OpenAI-Gemini request completed in ${duration}ms`)
} catch (error) {
logger.error('OpenAI-Gemini request error:', error);
logger.error('OpenAI-Gemini request error:', error)
// 处理速率限制
if (error.status === 429) {
if (req.apiKey && account && accountSelection) {
await unifiedGeminiScheduler.markAccountRateLimited(account.id, 'gemini', sessionHash);
await unifiedGeminiScheduler.markAccountRateLimited(account.id, 'gemini', sessionHash)
}
}
// 返回 OpenAI 格式的错误响应
const status = error.status || 500;
const status = error.status || 500
const errorResponse = {
error: error.error || {
message: error.message || 'Internal server error',
type: 'server_error',
code: 'internal_error'
}
};
res.status(status).json(errorResponse);
}
res.status(status).json(errorResponse)
} finally {
// 清理资源
if (abortController) {
abortController = null;
abortController = null
}
}
});
return undefined
})
// OpenAI 兼容的模型列表端点
router.get('/v1/models', authenticateApiKey, async (req, res) => {
try {
const apiKeyData = req.apiKey;
const apiKeyData = req.apiKey
// 检查权限
if (!checkPermissions(apiKeyData, 'gemini')) {
return res.status(403).json({
@@ -581,23 +616,27 @@ router.get('/v1/models', authenticateApiKey, async (req, res) => {
type: 'permission_denied',
code: 'permission_denied'
}
});
})
}
// 选择账户获取模型列表
let account = null;
let account = null
try {
const accountSelection = await unifiedGeminiScheduler.selectAccountForApiKey(apiKeyData, null, null);
account = await geminiAccountService.getAccount(accountSelection.accountId);
const accountSelection = await unifiedGeminiScheduler.selectAccountForApiKey(
apiKeyData,
null,
null
)
account = await geminiAccountService.getAccount(accountSelection.accountId)
} catch (error) {
logger.warn('Failed to select Gemini account for models endpoint:', error);
logger.warn('Failed to select Gemini account for models endpoint:', error)
}
let models = [];
let models = []
if (account) {
// 获取实际的模型列表
models = await getAvailableModels(account.accessToken, account.proxy);
models = await getAvailableModels(account.accessToken, account.proxy)
} else {
// 返回默认模型列表
models = [
@@ -607,37 +646,37 @@ router.get('/v1/models', authenticateApiKey, async (req, res) => {
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));
models = models.filter((model) => apiKeyData.restrictedModels.includes(model.id))
}
res.json({
object: 'list',
data: models
});
})
} catch (error) {
logger.error('Failed to get OpenAI-Gemini models:', error);
logger.error('Failed to get OpenAI-Gemini models:', error)
res.status(500).json({
error: {
message: 'Failed to retrieve models',
type: 'server_error',
code: 'internal_error'
}
});
})
}
});
return undefined
})
// OpenAI 兼容的模型详情端点
router.get('/v1/models/:model', authenticateApiKey, async (req, res) => {
try {
const apiKeyData = req.apiKey;
const modelId = req.params.model;
const apiKeyData = req.apiKey
const modelId = req.params.model
// 检查权限
if (!checkPermissions(apiKeyData, 'gemini')) {
return res.status(403).json({
@@ -646,9 +685,9 @@ router.get('/v1/models/:model', authenticateApiKey, async (req, res) => {
type: 'permission_denied',
code: 'permission_denied'
}
});
})
}
// 检查模型限制
if (apiKeyData.enableModelRestriction && apiKeyData.restrictedModels.length > 0) {
if (!apiKeyData.restrictedModels.includes(modelId)) {
@@ -658,10 +697,10 @@ router.get('/v1/models/:model', authenticateApiKey, async (req, res) => {
type: 'invalid_request_error',
code: 'model_not_found'
}
});
})
}
}
// 返回模型信息
res.json({
id: modelId,
@@ -671,18 +710,18 @@ router.get('/v1/models/:model', authenticateApiKey, async (req, res) => {
permission: [],
root: modelId,
parent: null
});
})
} catch (error) {
logger.error('Failed to get model details:', error);
logger.error('Failed to get model details:', error)
res.status(500).json({
error: {
message: 'Failed to retrieve model details',
type: 'server_error',
code: 'internal_error'
}
});
})
}
});
return undefined
})
module.exports = router;
module.exports = router