feat: 全面增强 Claude Code 客户端支持与错误处理

## 🚀 新功能
- **智能认证系统**: 根据 API Key 格式自动选择认证方式
  - `sk-ant-*` 开头使用 `x-api-key` 认证(兼容 Anthropic 官方)
  - 其他格式使用 `Authorization: Bearer` 认证(兼容标准 REST API)
- **Claude Code 客户端完整支持**: 新增必需的 API 端点
  - `GET /v1/models` - 返回支持的模型列表
  - `GET /v1/me` - 用户信息端点
  - `GET /v1/organizations/:org_id/usage` - 使用统计查询

## 🔧 修复与优化
- **HTTP 协议合规性**: 修复响应头冲突导致的 502 错误
  - 避免同时发送 `Content-Length` 和 `Transfer-Encoding` 头部
  - 优化响应头过滤机制,确保代理兼容性
- **完全透传错误响应**: 保持上游 API 原始响应格式
  - 透传原始状态码、响应头和内容
  - 移除错误包装,直接转发原始 JSON 格式
  - 支持流式和非流式请求的错误透传
- **流式响应处理优化**:
  - 添加 `validateStatus: () => true` 配置
  - 改进错误处理逻辑,避免异常中断

## 📝 代码质量
- 修复 ESLint 代码规范警告
- 优化敏感头部过滤列表
- 改进调试日志输出

## 🎯 解决的问题
- Claude Code 客户端无法连接(502 Bad Gateway)
- 错误响应被包装而非透传原始格式
- sk-ant-* 格式 API Key 认证失败
- HTTP/2 代理环境下的响应头冲突

##  测试验证
- 本地测试完全透传上游错误响应
- Claude Code 客户端连接测试通过
- 智能认证机制验证成功
- HTTP 协议合规性确认
This commit is contained in:
shinegod
2025-08-06 14:27:57 +00:00
parent 829a06f966
commit 3616245d49
3 changed files with 137 additions and 24 deletions

View File

@@ -86,11 +86,8 @@ class ClaudeConsoleRelayService {
data: modifiedRequestBody,
headers: {
'Content-Type': 'application/json',
'x-api-key': account.apiKey,
'anthropic-version': '2023-06-01',
'User-Agent': account.userAgent || this.defaultUserAgent,
'anthropic-dangerous-direct-browser-access': true,
'anthropic-beta': 'fine-grained-tool-streaming-2025-05-14',
...filteredHeaders
},
httpsAgent: proxyAgent,
@@ -99,6 +96,17 @@ class ClaudeConsoleRelayService {
validateStatus: () => true // 接受所有状态码
};
// 根据 API Key 格式选择认证方式
if (account.apiKey && account.apiKey.startsWith('sk-ant-')) {
// Anthropic 官方 API Key 使用 x-api-key
requestConfig.headers['x-api-key'] = account.apiKey;
logger.debug('[DEBUG] Using x-api-key authentication for sk-ant-* API key');
} else {
// 其他 API Key 使用 Authorization Bearer
requestConfig.headers['Authorization'] = `Bearer ${account.apiKey}`;
logger.debug('[DEBUG] Using Authorization Bearer authentication');
}
logger.debug(`[DEBUG] Initial headers before beta: ${JSON.stringify(requestConfig.headers, null, 2)}`);
// 添加beta header如果需要
@@ -230,18 +238,27 @@ class ClaudeConsoleRelayService {
data: body,
headers: {
'Content-Type': 'application/json',
'x-api-key': account.apiKey,
'anthropic-version': '2023-06-01',
'User-Agent': account.userAgent || this.defaultUserAgent,
'anthropic-dangerous-direct-browser-access': true,
'anthropic-beta': 'fine-grained-tool-streaming-2025-05-14',
...filteredHeaders
},
httpsAgent: proxyAgent,
timeout: config.proxy.timeout || 60000,
responseType: 'stream'
responseType: 'stream',
validateStatus: () => true // 接受所有状态码
};
// 根据 API Key 格式选择认证方式
if (account.apiKey && account.apiKey.startsWith('sk-ant-')) {
// Anthropic 官方 API Key 使用 x-api-key
requestConfig.headers['x-api-key'] = account.apiKey;
logger.debug('[DEBUG] Using x-api-key authentication for sk-ant-* API key');
} else {
// 其他 API Key 使用 Authorization Bearer
requestConfig.headers['Authorization'] = `Bearer ${account.apiKey}`;
logger.debug('[DEBUG] Using Authorization Bearer authentication');
}
// 添加beta header如果需要
if (requestOptions.betaHeader) {
requestConfig.headers['anthropic-beta'] = requestOptions.betaHeader;
@@ -261,24 +278,31 @@ class ClaudeConsoleRelayService {
claudeConsoleAccountService.markAccountRateLimited(accountId);
}
// 收集错误数据
let errorData = '';
// 设置错误响应的状态码和响应头
if (!responseStream.headersSent) {
const errorHeaders = {
'Content-Type': response.headers['content-type'] || 'application/json',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
};
// 避免 Transfer-Encoding 冲突,让 Express 自动处理
delete errorHeaders['Transfer-Encoding'];
delete errorHeaders['Content-Length'];
responseStream.writeHead(response.status, errorHeaders);
}
// 直接透传错误数据,不进行包装
response.data.on('data', chunk => {
errorData += chunk.toString();
if (!responseStream.destroyed) {
responseStream.write(chunk);
}
});
response.data.on('end', () => {
if (!responseStream.destroyed) {
responseStream.write('event: error\n');
responseStream.write(`data: ${JSON.stringify({
error: 'Claude Console API error',
status: response.status,
details: errorData,
timestamp: new Date().toISOString()
})}\n\n`);
responseStream.end();
}
reject(new Error(`Claude Console API error: ${response.status}`));
resolve(); // 不抛出异常,正常完成流处理
});
return;
}
@@ -459,15 +483,16 @@ class ClaudeConsoleRelayService {
_filterClientHeaders(clientHeaders) {
const sensitiveHeaders = [
'content-type',
"user-agent",
'x-api-key',
'user-agent',
'authorization',
'x-api-key',
'host',
'content-length',
'connection',
'proxy-authorization',
'content-encoding',
'transfer-encoding'
'transfer-encoding',
'anthropic-version'
];
const filteredHeaders = {};