mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
feat: 实现 Claude Code headers 动态管理功能
- 创建 claudeCodeHeadersService 管理各账号的 Claude Code headers - 自动捕获成功请求的 headers 并按账号存储在 Redis - 智能版本管理,只保留最新版本的 headers - OpenAI 转发时根据账号动态获取对应的 headers - 添加管理端点查看和清除各账号的 headers 信息 - 完整支持 Claude Code 必需的 beta headers 解决了 "This credential is only authorized for use with Claude Code" 错误 避免了固定版本号带来的风控问题 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ const logger = require('../utils/logger');
|
||||
const oauthHelper = require('../utils/oauthHelper');
|
||||
const CostCalculator = require('../utils/costCalculator');
|
||||
const pricingService = require('../services/pricingService');
|
||||
const claudeCodeHeadersService = require('../services/claudeCodeHeadersService');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -1558,4 +1559,52 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 📋 获取所有账号的 Claude Code headers 信息
|
||||
router.get('/claude-code-headers', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const allHeaders = await claudeCodeHeadersService.getAllAccountHeaders();
|
||||
|
||||
// 获取所有 Claude 账号信息
|
||||
const accounts = await claudeAccountService.getAllAccounts();
|
||||
const accountMap = {};
|
||||
accounts.forEach(account => {
|
||||
accountMap[account.id] = account.name;
|
||||
});
|
||||
|
||||
// 格式化输出
|
||||
const formattedData = Object.entries(allHeaders).map(([accountId, data]) => ({
|
||||
accountId,
|
||||
accountName: accountMap[accountId] || 'Unknown',
|
||||
version: data.version,
|
||||
userAgent: data.headers['user-agent'],
|
||||
updatedAt: data.updatedAt,
|
||||
headers: data.headers
|
||||
}));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: formattedData
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to get Claude Code headers:', error);
|
||||
res.status(500).json({ error: 'Failed to get Claude Code headers', message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 🗑️ 清除指定账号的 Claude Code headers
|
||||
router.delete('/claude-code-headers/:accountId', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { accountId } = req.params;
|
||||
await claudeCodeHeadersService.clearAccountHeaders(accountId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Claude Code headers cleared for account ${accountId}`
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to clear Claude Code headers:', error);
|
||||
res.status(500).json({ error: 'Failed to clear Claude Code headers', message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -12,6 +12,9 @@ const { authenticateApiKey } = require('../middleware/auth');
|
||||
const claudeRelayService = require('../services/claudeRelayService');
|
||||
const openaiToClaude = require('../services/openaiToClaude');
|
||||
const apiKeyService = require('../services/apiKeyService');
|
||||
const claudeAccountService = require('../services/claudeAccountService');
|
||||
const claudeCodeHeadersService = require('../services/claudeCodeHeadersService');
|
||||
const sessionHelper = require('../utils/sessionHelper');
|
||||
|
||||
// 加载模型定价数据
|
||||
let modelPricingData = {};
|
||||
@@ -199,6 +202,19 @@ async function handleChatCompletion(req, res, apiKeyData) {
|
||||
}
|
||||
}
|
||||
|
||||
// 生成会话哈希用于sticky会话
|
||||
const sessionHash = sessionHelper.generateSessionHash(claudeRequest);
|
||||
|
||||
// 选择可用的Claude账户
|
||||
const accountId = await claudeAccountService.selectAccountForApiKey(apiKeyData, sessionHash);
|
||||
|
||||
// 获取该账号存储的 Claude Code headers
|
||||
const claudeCodeHeaders = await claudeCodeHeadersService.getAccountHeaders(accountId);
|
||||
|
||||
logger.debug(`📋 Using Claude Code headers for account ${accountId}:`, {
|
||||
userAgent: claudeCodeHeaders['user-agent']
|
||||
});
|
||||
|
||||
// 处理流式请求
|
||||
if (claudeRequest.stream) {
|
||||
logger.info(`🌊 Processing OpenAI stream request for model: ${req.body.model}`);
|
||||
@@ -221,12 +237,12 @@ async function handleChatCompletion(req, res, apiKeyData) {
|
||||
}
|
||||
});
|
||||
|
||||
// 使用转换后的响应流 (使用 OAuth-only beta header,不传递客户端 headers)
|
||||
// 使用转换后的响应流 (使用 OAuth-only beta header,添加 Claude Code 必需的 headers)
|
||||
await claudeRelayService.relayStreamRequestWithUsageCapture(
|
||||
claudeRequest,
|
||||
apiKeyData,
|
||||
res,
|
||||
{},
|
||||
claudeCodeHeaders,
|
||||
(usage) => {
|
||||
// 记录使用统计
|
||||
if (usage && usage.input_tokens !== undefined && usage.output_tokens !== undefined) {
|
||||
@@ -252,20 +268,20 @@ async function handleChatCompletion(req, res, apiKeyData) {
|
||||
(chunk) => {
|
||||
return openaiToClaude.convertStreamChunk(chunk, req.body.model);
|
||||
},
|
||||
{ betaHeader: 'oauth-2025-04-20' }
|
||||
{ betaHeader: 'oauth-2025-04-20,claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14' }
|
||||
);
|
||||
|
||||
} else {
|
||||
// 非流式请求
|
||||
logger.info(`📄 Processing OpenAI non-stream request for model: ${req.body.model}`);
|
||||
|
||||
// 发送请求到 Claude (使用 OAuth-only beta header,不传递客户端 headers)
|
||||
// 发送请求到 Claude (使用 OAuth-only beta header,添加 Claude Code 必需的 headers)
|
||||
const claudeResponse = await claudeRelayService.relayRequest(
|
||||
claudeRequest,
|
||||
apiKeyData,
|
||||
req,
|
||||
res,
|
||||
{},
|
||||
claudeCodeHeaders,
|
||||
{ betaHeader: 'oauth-2025-04-20' }
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user