mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
fix: 统一格式化claude参数传递
This commit is contained in:
@@ -18,7 +18,7 @@ const { createClaudeTestPayload } = require('../utils/testPayloadHelper')
|
|||||||
|
|
||||||
class ClaudeRelayService {
|
class ClaudeRelayService {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.claudeApiUrl = config.claude.apiUrl
|
this.claudeApiUrl = 'https://api.anthropic.com/v1/messages?beta=true'
|
||||||
this.apiVersion = config.claude.apiVersion
|
this.apiVersion = config.claude.apiVersion
|
||||||
this.betaHeader = config.claude.betaHeader
|
this.betaHeader = config.claude.betaHeader
|
||||||
this.systemPrompt = config.claude.systemPrompt
|
this.systemPrompt = config.claude.systemPrompt
|
||||||
@@ -878,11 +878,102 @@ class ClaudeRelayService {
|
|||||||
|
|
||||||
// 🔧 过滤客户端请求头
|
// 🔧 过滤客户端请求头
|
||||||
_filterClientHeaders(clientHeaders) {
|
_filterClientHeaders(clientHeaders) {
|
||||||
// 使用统一的 headerFilter 工具类 - 移除 CDN、浏览器和代理相关 headers
|
// 使用统一的 headerFilter 工具类
|
||||||
// 同时伪装成正常的直接客户端请求,避免触发上游 API 的安全检查
|
// 同时伪装成正常的直接客户端请求,避免触发上游 API 的安全检查
|
||||||
return filterForClaude(clientHeaders)
|
return filterForClaude(clientHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔧 准备请求头和 payload(抽离公共逻辑)
|
||||||
|
async _prepareRequestHeadersAndPayload(
|
||||||
|
body,
|
||||||
|
clientHeaders,
|
||||||
|
accountId,
|
||||||
|
accessToken,
|
||||||
|
options = {}
|
||||||
|
) {
|
||||||
|
const { account, accountType, sessionHash, requestOptions = {}, isStream = false } = options
|
||||||
|
|
||||||
|
// 获取统一的 User-Agent
|
||||||
|
const unifiedUA = await this.captureAndGetUnifiedUserAgent(clientHeaders, account)
|
||||||
|
|
||||||
|
// 获取过滤后的客户端 headers
|
||||||
|
const filteredHeaders = this._filterClientHeaders(clientHeaders)
|
||||||
|
|
||||||
|
// 判断是否是真实的 Claude Code 请求
|
||||||
|
const isRealClaudeCode = this.isRealClaudeCodeRequest(body)
|
||||||
|
|
||||||
|
// 如果不是真实的 Claude Code 请求,需要使用从账户获取的 Claude Code headers
|
||||||
|
let finalHeaders = { ...filteredHeaders }
|
||||||
|
let requestPayload = body
|
||||||
|
|
||||||
|
if (!isRealClaudeCode) {
|
||||||
|
// 获取该账号存储的 Claude Code headers
|
||||||
|
const claudeCodeHeaders = await claudeCodeHeadersService.getAccountHeaders(accountId)
|
||||||
|
|
||||||
|
// 只添加客户端没有提供的 headers
|
||||||
|
Object.keys(claudeCodeHeaders).forEach((key) => {
|
||||||
|
const lowerKey = key.toLowerCase()
|
||||||
|
if (!finalHeaders[key] && !finalHeaders[lowerKey]) {
|
||||||
|
finalHeaders[key] = claudeCodeHeaders[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用请求身份转换
|
||||||
|
const extensionResult = this._applyRequestIdentityTransform(requestPayload, finalHeaders, {
|
||||||
|
account,
|
||||||
|
accountId,
|
||||||
|
accountType,
|
||||||
|
sessionHash,
|
||||||
|
clientHeaders,
|
||||||
|
requestOptions,
|
||||||
|
isStream
|
||||||
|
})
|
||||||
|
|
||||||
|
if (extensionResult.abortResponse) {
|
||||||
|
return { abortResponse: extensionResult.abortResponse }
|
||||||
|
}
|
||||||
|
|
||||||
|
requestPayload = extensionResult.body
|
||||||
|
finalHeaders = extensionResult.headers
|
||||||
|
|
||||||
|
// 序列化请求体,计算 content-length
|
||||||
|
const bodyString = JSON.stringify(requestPayload)
|
||||||
|
const contentLength = Buffer.byteLength(bodyString, 'utf8')
|
||||||
|
|
||||||
|
// 构建最终请求头(包含认证、版本、User-Agent、Beta 等)
|
||||||
|
const headers = {
|
||||||
|
host: 'api.anthropic.com',
|
||||||
|
connection: 'keep-alive',
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'content-length': String(contentLength),
|
||||||
|
authorization: `Bearer ${accessToken}`,
|
||||||
|
'anthropic-version': this.apiVersion,
|
||||||
|
...finalHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用统一 User-Agent 或客户端提供的,最后使用默认值
|
||||||
|
const userAgent = unifiedUA || headers['user-agent'] || 'claude-cli/1.0.119 (external, cli)'
|
||||||
|
const acceptHeader = headers['accept'] || 'application/json'
|
||||||
|
delete headers['user-agent']
|
||||||
|
delete headers['accept']
|
||||||
|
headers['User-Agent'] = userAgent
|
||||||
|
headers['Accept'] = acceptHeader
|
||||||
|
|
||||||
|
logger.info(`🔗 指纹是这个: ${headers['User-Agent']}`)
|
||||||
|
|
||||||
|
// 根据模型和客户端传递的 anthropic-beta 动态设置 header
|
||||||
|
const modelId = requestPayload?.model || body?.model
|
||||||
|
const clientBetaHeader = clientHeaders?.['anthropic-beta']
|
||||||
|
headers['anthropic-beta'] = this._getBetaHeader(modelId, clientBetaHeader)
|
||||||
|
return {
|
||||||
|
requestPayload,
|
||||||
|
bodyString,
|
||||||
|
headers,
|
||||||
|
isRealClaudeCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_applyRequestIdentityTransform(body, headers, context = {}) {
|
_applyRequestIdentityTransform(body, headers, context = {}) {
|
||||||
const normalizedHeaders = headers && typeof headers === 'object' ? { ...headers } : {}
|
const normalizedHeaders = headers && typeof headers === 'object' ? { ...headers } : {}
|
||||||
|
|
||||||
@@ -928,46 +1019,24 @@ class ClaudeRelayService {
|
|||||||
// 获取账户信息用于统一 User-Agent
|
// 获取账户信息用于统一 User-Agent
|
||||||
const account = await claudeAccountService.getAccount(accountId)
|
const account = await claudeAccountService.getAccount(accountId)
|
||||||
|
|
||||||
// 获取统一的 User-Agent
|
// 使用公共方法准备请求头和 payload
|
||||||
const unifiedUA = await this.captureAndGetUnifiedUserAgent(clientHeaders, account)
|
const prepared = await this._prepareRequestHeadersAndPayload(
|
||||||
|
body,
|
||||||
// 获取过滤后的客户端 headers
|
|
||||||
const filteredHeaders = this._filterClientHeaders(clientHeaders)
|
|
||||||
|
|
||||||
// 判断是否是真实的 Claude Code 请求
|
|
||||||
const isRealClaudeCode = this.isRealClaudeCodeRequest(body)
|
|
||||||
|
|
||||||
// 如果不是真实的 Claude Code 请求,需要使用从账户获取的 Claude Code headers
|
|
||||||
let finalHeaders = { ...filteredHeaders }
|
|
||||||
let requestPayload = body
|
|
||||||
|
|
||||||
if (!isRealClaudeCode) {
|
|
||||||
// 获取该账号存储的 Claude Code headers
|
|
||||||
const claudeCodeHeaders = await claudeCodeHeadersService.getAccountHeaders(accountId)
|
|
||||||
|
|
||||||
// 只添加客户端没有提供的 headers
|
|
||||||
Object.keys(claudeCodeHeaders).forEach((key) => {
|
|
||||||
const lowerKey = key.toLowerCase()
|
|
||||||
if (!finalHeaders[key] && !finalHeaders[lowerKey]) {
|
|
||||||
finalHeaders[key] = claudeCodeHeaders[key]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const extensionResult = this._applyRequestIdentityTransform(requestPayload, finalHeaders, {
|
|
||||||
account,
|
|
||||||
accountId,
|
|
||||||
clientHeaders,
|
clientHeaders,
|
||||||
requestOptions,
|
accountId,
|
||||||
isStream: false
|
accessToken,
|
||||||
})
|
{
|
||||||
|
account,
|
||||||
|
requestOptions,
|
||||||
|
isStream: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (extensionResult.abortResponse) {
|
if (prepared.abortResponse) {
|
||||||
return extensionResult.abortResponse
|
return prepared.abortResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
requestPayload = extensionResult.body
|
const { bodyString, headers } = prepared
|
||||||
finalHeaders = extensionResult.headers
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// 支持自定义路径(如 count_tokens)
|
// 支持自定义路径(如 count_tokens)
|
||||||
@@ -981,30 +1050,14 @@ class ClaudeRelayService {
|
|||||||
const options = {
|
const options = {
|
||||||
hostname: url.hostname,
|
hostname: url.hostname,
|
||||||
port: url.port || 443,
|
port: url.port || 443,
|
||||||
path: requestPath,
|
path: requestPath + (url.search || ''),
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers,
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
'anthropic-version': this.apiVersion,
|
|
||||||
...finalHeaders
|
|
||||||
},
|
|
||||||
agent: proxyAgent,
|
agent: proxyAgent,
|
||||||
timeout: config.requestTimeout || 600000
|
timeout: config.requestTimeout || 600000
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用统一 User-Agent 或客户端提供的,最后使用默认值
|
console.log(options.path)
|
||||||
if (!options.headers['user-agent'] || unifiedUA !== null) {
|
|
||||||
const userAgent = unifiedUA || 'claude-cli/1.0.119 (external, cli)'
|
|
||||||
options.headers['user-agent'] = userAgent
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`🔗 指纹是这个: ${options.headers['user-agent']}`)
|
|
||||||
|
|
||||||
// 根据模型和客户端传递的 anthropic-beta 动态设置 header
|
|
||||||
const modelId = requestPayload?.model || body?.model
|
|
||||||
const clientBetaHeader = clientHeaders?.['anthropic-beta']
|
|
||||||
options.headers['anthropic-beta'] = this._getBetaHeader(modelId, clientBetaHeader)
|
|
||||||
|
|
||||||
const req = https.request(options, (res) => {
|
const req = https.request(options, (res) => {
|
||||||
let responseData = Buffer.alloc(0)
|
let responseData = Buffer.alloc(0)
|
||||||
@@ -1015,32 +1068,32 @@ class ClaudeRelayService {
|
|||||||
|
|
||||||
res.on('end', () => {
|
res.on('end', () => {
|
||||||
try {
|
try {
|
||||||
let bodyString = ''
|
let responseBody = ''
|
||||||
|
|
||||||
// 根据Content-Encoding处理响应数据
|
// 根据Content-Encoding处理响应数据
|
||||||
const contentEncoding = res.headers['content-encoding']
|
const contentEncoding = res.headers['content-encoding']
|
||||||
if (contentEncoding === 'gzip') {
|
if (contentEncoding === 'gzip') {
|
||||||
try {
|
try {
|
||||||
bodyString = zlib.gunzipSync(responseData).toString('utf8')
|
responseBody = zlib.gunzipSync(responseData).toString('utf8')
|
||||||
} catch (unzipError) {
|
} catch (unzipError) {
|
||||||
logger.error('❌ Failed to decompress gzip response:', unzipError)
|
logger.error('❌ Failed to decompress gzip response:', unzipError)
|
||||||
bodyString = responseData.toString('utf8')
|
responseBody = responseData.toString('utf8')
|
||||||
}
|
}
|
||||||
} else if (contentEncoding === 'deflate') {
|
} else if (contentEncoding === 'deflate') {
|
||||||
try {
|
try {
|
||||||
bodyString = zlib.inflateSync(responseData).toString('utf8')
|
responseBody = zlib.inflateSync(responseData).toString('utf8')
|
||||||
} catch (unzipError) {
|
} catch (unzipError) {
|
||||||
logger.error('❌ Failed to decompress deflate response:', unzipError)
|
logger.error('❌ Failed to decompress deflate response:', unzipError)
|
||||||
bodyString = responseData.toString('utf8')
|
responseBody = responseData.toString('utf8')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bodyString = responseData.toString('utf8')
|
responseBody = responseData.toString('utf8')
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
statusCode: res.statusCode,
|
statusCode: res.statusCode,
|
||||||
headers: res.headers,
|
headers: res.headers,
|
||||||
body: bodyString
|
body: responseBody
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`🔗 Claude API response: ${res.statusCode}`)
|
logger.debug(`🔗 Claude API response: ${res.statusCode}`)
|
||||||
@@ -1095,7 +1148,7 @@ class ClaudeRelayService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 写入请求体
|
// 写入请求体
|
||||||
req.write(JSON.stringify(requestPayload))
|
req.write(bodyString)
|
||||||
req.end()
|
req.end()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1248,79 +1301,39 @@ class ClaudeRelayService {
|
|||||||
const isOpusModelRequest =
|
const isOpusModelRequest =
|
||||||
typeof body?.model === 'string' && body.model.toLowerCase().includes('opus')
|
typeof body?.model === 'string' && body.model.toLowerCase().includes('opus')
|
||||||
|
|
||||||
// 获取统一的 User-Agent
|
// 使用公共方法准备请求头和 payload
|
||||||
const unifiedUA = await this.captureAndGetUnifiedUserAgent(clientHeaders, account)
|
const prepared = await this._prepareRequestHeadersAndPayload(
|
||||||
|
body,
|
||||||
// 获取过滤后的客户端 headers
|
|
||||||
const filteredHeaders = this._filterClientHeaders(clientHeaders)
|
|
||||||
|
|
||||||
// 判断是否是真实的 Claude Code 请求
|
|
||||||
const isRealClaudeCode = this.isRealClaudeCodeRequest(body)
|
|
||||||
|
|
||||||
// 如果不是真实的 Claude Code 请求,需要使用从账户获取的 Claude Code headers
|
|
||||||
let finalHeaders = { ...filteredHeaders }
|
|
||||||
let requestPayload = body
|
|
||||||
|
|
||||||
if (!isRealClaudeCode) {
|
|
||||||
// 获取该账号存储的 Claude Code headers
|
|
||||||
const claudeCodeHeaders = await claudeCodeHeadersService.getAccountHeaders(accountId)
|
|
||||||
|
|
||||||
// 只添加客户端没有提供的 headers
|
|
||||||
Object.keys(claudeCodeHeaders).forEach((key) => {
|
|
||||||
const lowerKey = key.toLowerCase()
|
|
||||||
if (!finalHeaders[key] && !finalHeaders[lowerKey]) {
|
|
||||||
finalHeaders[key] = claudeCodeHeaders[key]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const extensionResult = this._applyRequestIdentityTransform(requestPayload, finalHeaders, {
|
|
||||||
account,
|
|
||||||
accountId,
|
|
||||||
accountType,
|
|
||||||
sessionHash,
|
|
||||||
clientHeaders,
|
clientHeaders,
|
||||||
requestOptions,
|
accountId,
|
||||||
isStream: true
|
accessToken,
|
||||||
})
|
{
|
||||||
|
account,
|
||||||
|
accountType,
|
||||||
|
sessionHash,
|
||||||
|
requestOptions,
|
||||||
|
isStream: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (extensionResult.abortResponse) {
|
if (prepared.abortResponse) {
|
||||||
return extensionResult.abortResponse
|
return prepared.abortResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
requestPayload = extensionResult.body
|
const { bodyString, headers } = prepared
|
||||||
finalHeaders = extensionResult.headers
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const url = new URL(this.claudeApiUrl)
|
const url = new URL(this.claudeApiUrl)
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
hostname: url.hostname,
|
hostname: url.hostname,
|
||||||
port: url.port || 443,
|
port: url.port || 443,
|
||||||
path: url.pathname,
|
path: url.pathname + (url.search || ''),
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers,
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
'anthropic-version': this.apiVersion,
|
|
||||||
...finalHeaders
|
|
||||||
},
|
|
||||||
agent: proxyAgent,
|
agent: proxyAgent,
|
||||||
timeout: config.requestTimeout || 600000
|
timeout: config.requestTimeout || 600000
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用统一 User-Agent 或客户端提供的,最后使用默认值
|
|
||||||
if (!options.headers['user-agent'] || unifiedUA !== null) {
|
|
||||||
const userAgent = unifiedUA || 'claude-cli/1.0.119 (external, cli)'
|
|
||||||
options.headers['user-agent'] = userAgent
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`🔗 指纹是这个: ${options.headers['user-agent']}`)
|
|
||||||
// 根据模型和客户端传递的 anthropic-beta 动态设置 header
|
|
||||||
const modelId = body?.model
|
|
||||||
const clientBetaHeader = clientHeaders?.['anthropic-beta']
|
|
||||||
options.headers['anthropic-beta'] = this._getBetaHeader(modelId, clientBetaHeader)
|
|
||||||
|
|
||||||
const req = https.request(options, async (res) => {
|
const req = https.request(options, async (res) => {
|
||||||
logger.debug(`🌊 Claude stream response status: ${res.statusCode}`)
|
logger.debug(`🌊 Claude stream response status: ${res.statusCode}`)
|
||||||
|
|
||||||
@@ -1766,15 +1779,15 @@ class ClaudeRelayService {
|
|||||||
|
|
||||||
// 提取5小时会话窗口状态
|
// 提取5小时会话窗口状态
|
||||||
// 使用大小写不敏感的方式获取响应头
|
// 使用大小写不敏感的方式获取响应头
|
||||||
const get5hStatus = (headers) => {
|
const get5hStatus = (resHeaders) => {
|
||||||
if (!headers) {
|
if (!resHeaders) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// HTTP头部名称不区分大小写,需要处理不同情况
|
// HTTP头部名称不区分大小写,需要处理不同情况
|
||||||
return (
|
return (
|
||||||
headers['anthropic-ratelimit-unified-5h-status'] ||
|
resHeaders['anthropic-ratelimit-unified-5h-status'] ||
|
||||||
headers['Anthropic-Ratelimit-Unified-5h-Status'] ||
|
resHeaders['Anthropic-Ratelimit-Unified-5h-Status'] ||
|
||||||
headers['ANTHROPIC-RATELIMIT-UNIFIED-5H-STATUS']
|
resHeaders['ANTHROPIC-RATELIMIT-UNIFIED-5H-STATUS']
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1942,7 +1955,7 @@ class ClaudeRelayService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 写入请求体
|
// 写入请求体
|
||||||
req.write(JSON.stringify(requestPayload))
|
req.write(bodyString)
|
||||||
req.end()
|
req.end()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,18 @@ const STAINLESS_HEADER_KEYS = [
|
|||||||
'x-stainless-runtime',
|
'x-stainless-runtime',
|
||||||
'x-stainless-runtime-version'
|
'x-stainless-runtime-version'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 小写 key 到正确大小写格式的映射(用于返回给上游时)
|
||||||
|
const STAINLESS_HEADER_CASE_MAP = {
|
||||||
|
'x-stainless-retry-count': 'X-Stainless-Retry-Count',
|
||||||
|
'x-stainless-timeout': 'X-Stainless-Timeout',
|
||||||
|
'x-stainless-lang': 'X-Stainless-Lang',
|
||||||
|
'x-stainless-package-version': 'X-Stainless-Package-Version',
|
||||||
|
'x-stainless-os': 'X-Stainless-OS',
|
||||||
|
'x-stainless-arch': 'X-Stainless-Arch',
|
||||||
|
'x-stainless-runtime': 'X-Stainless-Runtime',
|
||||||
|
'x-stainless-runtime-version': 'X-Stainless-Runtime-Version'
|
||||||
|
}
|
||||||
const MIN_FINGERPRINT_FIELDS = 4
|
const MIN_FINGERPRINT_FIELDS = 4
|
||||||
const REDIS_KEY_PREFIX = 'fmt_claude_req:stainless_headers:'
|
const REDIS_KEY_PREFIX = 'fmt_claude_req:stainless_headers:'
|
||||||
|
|
||||||
@@ -135,7 +147,9 @@ function applyFingerprintToHeaders(headers, fingerprint) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
removeHeaderCaseInsensitive(nextHeaders, key)
|
removeHeaderCaseInsensitive(nextHeaders, key)
|
||||||
nextHeaders[key] = fingerprint[key]
|
// 使用正确的大小写格式返回给上游
|
||||||
|
const properCaseKey = STAINLESS_HEADER_CASE_MAP[key] || key
|
||||||
|
nextHeaders[properCaseKey] = fingerprint[key]
|
||||||
})
|
})
|
||||||
|
|
||||||
return nextHeaders
|
return nextHeaders
|
||||||
|
|||||||
@@ -52,50 +52,38 @@ function filterForOpenAI(headers) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 为 Claude/Anthropic API 过滤 headers
|
* 为 Claude/Anthropic API 过滤 headers
|
||||||
* 在原有逻辑基础上添加 CDN headers 到敏感列表
|
* 使用白名单模式,只允许指定的 headers 通过
|
||||||
*/
|
*/
|
||||||
function filterForClaude(headers) {
|
function filterForClaude(headers) {
|
||||||
const sensitiveHeaders = [
|
// 白名单模式:只允许以下 headers
|
||||||
'content-type',
|
const allowedHeaders = [
|
||||||
'user-agent',
|
|
||||||
'x-api-key',
|
|
||||||
'authorization',
|
|
||||||
'x-authorization',
|
|
||||||
'host',
|
|
||||||
'content-length',
|
|
||||||
'connection',
|
|
||||||
'proxy-authorization',
|
|
||||||
'content-encoding',
|
|
||||||
'transfer-encoding',
|
|
||||||
...cdnHeaders // 添加 CDN headers
|
|
||||||
]
|
|
||||||
|
|
||||||
const browserHeaders = [
|
|
||||||
'origin',
|
|
||||||
'referer',
|
|
||||||
'sec-fetch-mode',
|
|
||||||
'sec-fetch-site',
|
|
||||||
'sec-fetch-dest',
|
|
||||||
'sec-ch-ua',
|
|
||||||
'sec-ch-ua-mobile',
|
|
||||||
'sec-ch-ua-platform',
|
|
||||||
'accept-language',
|
|
||||||
'accept-encoding',
|
|
||||||
'accept',
|
'accept',
|
||||||
'cache-control',
|
'x-stainless-retry-count',
|
||||||
'pragma',
|
'x-stainless-timeout',
|
||||||
'anthropic-dangerous-direct-browser-access'
|
'x-stainless-lang',
|
||||||
|
'x-stainless-package-version',
|
||||||
|
'x-stainless-os',
|
||||||
|
'x-stainless-arch',
|
||||||
|
'x-stainless-runtime',
|
||||||
|
'x-stainless-runtime-version',
|
||||||
|
'x-stainless-helper-method',
|
||||||
|
'anthropic-dangerous-direct-browser-access',
|
||||||
|
'anthropic-version',
|
||||||
|
'x-app',
|
||||||
|
'anthropic-beta',
|
||||||
|
'accept-language',
|
||||||
|
'sec-fetch-mode',
|
||||||
|
'accept-encoding',
|
||||||
|
'user-agent',
|
||||||
|
'content-type',
|
||||||
|
'connection'
|
||||||
]
|
]
|
||||||
|
|
||||||
const allowedHeaders = ['x-request-id', 'anthropic-version', 'anthropic-beta']
|
|
||||||
|
|
||||||
const filtered = {}
|
const filtered = {}
|
||||||
Object.keys(headers || {}).forEach((key) => {
|
Object.keys(headers || {}).forEach((key) => {
|
||||||
const lowerKey = key.toLowerCase()
|
const lowerKey = key.toLowerCase()
|
||||||
if (allowedHeaders.includes(lowerKey)) {
|
if (allowedHeaders.includes(lowerKey)) {
|
||||||
filtered[key] = headers[key]
|
filtered[key] = headers[key]
|
||||||
} else if (!sensitiveHeaders.includes(lowerKey) && !browserHeaders.includes(lowerKey)) {
|
|
||||||
filtered[key] = headers[key]
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user