Merge PR #512: 添加 OpenAI chat/completions 兼容支持

## 主要功能
-  使用策略模式处理不同后端(Claude/OpenAI/Gemini)
-  添加 OpenAI chat/completions 兼容支持
-  修复代码缩进符合 ESLint 规范

## 核心变更

### 1. 后端检测机制
添加 `detectBackendFromModel()` 函数:
- 根据模型名称前缀检测后端(claude-/gpt-/gemini-)
- 默认使用 Claude 后端

### 2. 扩展模型列表
/v1/models 端点现在返回:
- Claude 模型:Sonnet 4.5, Opus 4.1, Sonnet 4, Haiku等
- OpenAI 模型:gpt-4o, gpt-4o-mini, gpt-4-turbo等
- Gemini 模型:gemini-1.5-pro, gemini-1.5-flash等

### 3. OpenAI 兼容支持
- 添加 `validateChatCompletionRequest()` 验证函数
- 支持 OpenAI chat/completions 请求格式
- 实现流式和非流式响应处理

### 4. 代码规范
- 修复 ESLint 缩进问题
- 统一代码格式

## 技术细节
- 修改文件:src/routes/api.js, src/services/openaiToClaude.js 等
- 版本更新:1.1.168 → 1.1.169
- 保留了之前添加的账户过期检查逻辑(来自PR #541)

作者: bottotl
PR: https://github.com/Wei-Shaw/claude-relay-service/pull/512
This commit is contained in:
shaw
2025-10-12 13:49:20 +08:00

View File

@@ -778,6 +778,29 @@ class ClaudeAccountService {
}
}
/**
* 检查账户是否未过期
* @param {Object} account - 账户对象
* @returns {boolean} - 如果未设置过期时间或未过期返回 true
*/
isAccountNotExpired(account) {
if (!account.subscriptionExpiresAt) {
return true // 未设置过期时间,视为永不过期
}
const expiryDate = new Date(account.subscriptionExpiresAt)
const now = new Date()
if (expiryDate <= now) {
logger.debug(
`⏰ Account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}`
)
return false
}
return true
}
// 🎯 智能选择可用账户支持sticky会话和模型过滤
async selectAvailableAccount(sessionHash = null, modelName = null) {
try {
@@ -787,7 +810,8 @@ class ClaudeAccountService {
(account) =>
account.isActive === 'true' &&
account.status !== 'error' &&
account.schedulable !== 'false'
account.schedulable !== 'false' &&
this.isAccountNotExpired(account)
)
// 如果请求的是 Opus 模型,过滤掉 Pro 和 Free 账号
@@ -882,7 +906,8 @@ class ClaudeAccountService {
boundAccount &&
boundAccount.isActive === 'true' &&
boundAccount.status !== 'error' &&
boundAccount.schedulable !== 'false'
boundAccount.schedulable !== 'false' &&
this.isAccountNotExpired(boundAccount)
) {
logger.info(
`🎯 Using bound dedicated account: ${boundAccount.name} (${apiKeyData.claudeAccountId}) for API key ${apiKeyData.name}`
@@ -903,7 +928,8 @@ class ClaudeAccountService {
account.isActive === 'true' &&
account.status !== 'error' &&
account.schedulable !== 'false' &&
(account.accountType === 'shared' || !account.accountType) // 兼容旧数据
(account.accountType === 'shared' || !account.accountType) && // 兼容旧数据
this.isAccountNotExpired(account)
)
// 如果请求的是 Opus 模型,过滤掉 Pro 和 Free 账号