mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
style: format all files with prettier
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,8 @@ const repository =
|
|||||||
process.env.PRICE_MIRROR_REPO || process.env.GITHUB_REPOSITORY || 'Wei-Shaw/claude-relay-service'
|
process.env.PRICE_MIRROR_REPO || process.env.GITHUB_REPOSITORY || 'Wei-Shaw/claude-relay-service'
|
||||||
const branch = process.env.PRICE_MIRROR_BRANCH || 'price-mirror'
|
const branch = process.env.PRICE_MIRROR_BRANCH || 'price-mirror'
|
||||||
const pricingFileName = process.env.PRICE_MIRROR_FILENAME || 'model_prices_and_context_window.json'
|
const pricingFileName = process.env.PRICE_MIRROR_FILENAME || 'model_prices_and_context_window.json'
|
||||||
const hashFileName = process.env.PRICE_MIRROR_HASH_FILENAME || 'model_prices_and_context_window.sha256'
|
const hashFileName =
|
||||||
|
process.env.PRICE_MIRROR_HASH_FILENAME || 'model_prices_and_context_window.sha256'
|
||||||
|
|
||||||
const baseUrl = process.env.PRICE_MIRROR_BASE_URL
|
const baseUrl = process.env.PRICE_MIRROR_BASE_URL
|
||||||
? process.env.PRICE_MIRROR_BASE_URL.replace(/\/$/, '')
|
? process.env.PRICE_MIRROR_BASE_URL.replace(/\/$/, '')
|
||||||
@@ -11,7 +12,6 @@ const baseUrl = process.env.PRICE_MIRROR_BASE_URL
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
pricingFileName,
|
pricingFileName,
|
||||||
hashFileName,
|
hashFileName,
|
||||||
pricingUrl:
|
pricingUrl: process.env.PRICE_MIRROR_JSON_URL || `${baseUrl}/${pricingFileName}`,
|
||||||
process.env.PRICE_MIRROR_JSON_URL || `${baseUrl}/${pricingFileName}`,
|
|
||||||
hashUrl: process.env.PRICE_MIRROR_HASH_URL || `${baseUrl}/${hashFileName}`
|
hashUrl: process.env.PRICE_MIRROR_HASH_URL || `${baseUrl}/${hashFileName}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1193,7 +1193,9 @@ async function importData() {
|
|||||||
pipeline.hset(`usage:global:daily:${date}`, field, value)
|
pipeline.hset(`usage:global:daily:${date}`, field, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.info(`📥 Importing ${Object.keys(globalStats.globalDaily).length} global daily stats`)
|
logger.info(
|
||||||
|
`📥 Importing ${Object.keys(globalStats.globalDaily).length} global daily stats`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导入全局每月统计
|
// 导入全局每月统计
|
||||||
@@ -1203,7 +1205,9 @@ async function importData() {
|
|||||||
pipeline.hset(`usage:global:monthly:${month}`, field, value)
|
pipeline.hset(`usage:global:monthly:${month}`, field, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.info(`📥 Importing ${Object.keys(globalStats.globalMonthly).length} global monthly stats`)
|
logger.info(
|
||||||
|
`📥 Importing ${Object.keys(globalStats.globalMonthly).length} global monthly stats`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导入每日统计
|
// 导入每日统计
|
||||||
|
|||||||
@@ -1473,8 +1473,12 @@ class RedisClient {
|
|||||||
// 多抽样检查:抽取最多 3 个 keyId 检查是否有 alltime 数据
|
// 多抽样检查:抽取最多 3 个 keyId 检查是否有 alltime 数据
|
||||||
const sampleIndices = new Set()
|
const sampleIndices = new Set()
|
||||||
sampleIndices.add(0) // 始终包含第一个
|
sampleIndices.add(0) // 始终包含第一个
|
||||||
if (keyIds.length > 1) sampleIndices.add(keyIds.length - 1) // 包含最后一个
|
if (keyIds.length > 1) {
|
||||||
if (keyIds.length > 2) sampleIndices.add(Math.floor(keyIds.length / 2)) // 包含中间一个
|
sampleIndices.add(keyIds.length - 1)
|
||||||
|
} // 包含最后一个
|
||||||
|
if (keyIds.length > 2) {
|
||||||
|
sampleIndices.add(Math.floor(keyIds.length / 2))
|
||||||
|
} // 包含中间一个
|
||||||
|
|
||||||
let hasAnyAlltimeData = false
|
let hasAnyAlltimeData = false
|
||||||
for (const idx of sampleIndices) {
|
for (const idx of sampleIndices) {
|
||||||
|
|||||||
@@ -2673,13 +2673,14 @@ router.get('/api-keys/:keyId/usage-records', authenticateAdmin, async (req, res)
|
|||||||
costFormatted: CostCalculator.formatCost(computedCost),
|
costFormatted: CostCalculator.formatCost(computedCost),
|
||||||
realCost: Number(realCost.toFixed(6)),
|
realCost: Number(realCost.toFixed(6)),
|
||||||
realCostFormatted: CostCalculator.formatCost(realCost),
|
realCostFormatted: CostCalculator.formatCost(realCost),
|
||||||
costBreakdown: record.realCostBreakdown || record.costBreakdown || {
|
costBreakdown: record.realCostBreakdown ||
|
||||||
input: costData?.costs?.input || 0,
|
record.costBreakdown || {
|
||||||
output: costData?.costs?.output || 0,
|
input: costData?.costs?.input || 0,
|
||||||
cacheCreate: costData?.costs?.cacheWrite || 0,
|
output: costData?.costs?.output || 0,
|
||||||
cacheRead: costData?.costs?.cacheRead || 0,
|
cacheCreate: costData?.costs?.cacheWrite || 0,
|
||||||
total: costData?.costs?.total || computedCost
|
cacheRead: costData?.costs?.cacheRead || 0,
|
||||||
},
|
total: costData?.costs?.total || computedCost
|
||||||
|
},
|
||||||
responseTime: record.responseTime || null
|
responseTime: record.responseTime || null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2986,13 +2987,14 @@ router.get('/accounts/:accountId/usage-records', authenticateAdmin, async (req,
|
|||||||
costFormatted: CostCalculator.formatCost(computedCost),
|
costFormatted: CostCalculator.formatCost(computedCost),
|
||||||
realCost: Number(realCost.toFixed(6)),
|
realCost: Number(realCost.toFixed(6)),
|
||||||
realCostFormatted: CostCalculator.formatCost(realCost),
|
realCostFormatted: CostCalculator.formatCost(realCost),
|
||||||
costBreakdown: record.realCostBreakdown || record.costBreakdown || {
|
costBreakdown: record.realCostBreakdown ||
|
||||||
input: costData?.costs?.input || 0,
|
record.costBreakdown || {
|
||||||
output: costData?.costs?.output || 0,
|
input: costData?.costs?.input || 0,
|
||||||
cacheCreate: costData?.costs?.cacheWrite || 0,
|
output: costData?.costs?.output || 0,
|
||||||
cacheRead: costData?.costs?.cacheRead || 0,
|
cacheCreate: costData?.costs?.cacheWrite || 0,
|
||||||
total: costData?.costs?.total || computedCost
|
cacheRead: costData?.costs?.cacheRead || 0,
|
||||||
},
|
total: costData?.costs?.total || computedCost
|
||||||
|
},
|
||||||
responseTime: record.responseTime || null
|
responseTime: record.responseTime || null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,14 @@ const {
|
|||||||
} = require('../services/anthropicGeminiBridgeService')
|
} = require('../services/anthropicGeminiBridgeService')
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
function queueRateLimitUpdate(rateLimitInfo, usageSummary, model, context = '', keyId = null, accountType = null) {
|
function queueRateLimitUpdate(
|
||||||
|
rateLimitInfo,
|
||||||
|
usageSummary,
|
||||||
|
model,
|
||||||
|
context = '',
|
||||||
|
keyId = null,
|
||||||
|
accountType = null
|
||||||
|
) {
|
||||||
if (!rateLimitInfo) {
|
if (!rateLimitInfo) {
|
||||||
return Promise.resolve({ totalTokens: 0, totalCost: 0 })
|
return Promise.resolve({ totalTokens: 0, totalCost: 0 })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -828,7 +828,7 @@ router.post('/api/batch-model-stats', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 优先使用存储的费用,否则回退到重新计算
|
// 优先使用存储的费用,否则回退到重新计算
|
||||||
const hasStoredCost = usage.hasStoredCost
|
const { hasStoredCost } = usage
|
||||||
const costData = CostCalculator.calculateCost(usageData, model)
|
const costData = CostCalculator.calculateCost(usageData, model)
|
||||||
|
|
||||||
// 如果有存储的费用,覆盖计算的费用
|
// 如果有存储的费用,覆盖计算的费用
|
||||||
|
|||||||
@@ -24,7 +24,14 @@ function checkPermissions(apiKeyData, requiredPermission = 'claude') {
|
|||||||
return apiKeyService.hasPermission(apiKeyData?.permissions, requiredPermission)
|
return apiKeyService.hasPermission(apiKeyData?.permissions, requiredPermission)
|
||||||
}
|
}
|
||||||
|
|
||||||
function queueRateLimitUpdate(rateLimitInfo, usageSummary, model, context = '', keyId = null, accountType = null) {
|
function queueRateLimitUpdate(
|
||||||
|
rateLimitInfo,
|
||||||
|
usageSummary,
|
||||||
|
model,
|
||||||
|
context = '',
|
||||||
|
keyId = null,
|
||||||
|
accountType = null
|
||||||
|
) {
|
||||||
if (!rateLimitInfo) {
|
if (!rateLimitInfo) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1800,7 +1800,13 @@ function dumpToolsPayload({ vendor, model, tools, toolChoice }) {
|
|||||||
* 更新速率限制计数器
|
* 更新速率限制计数器
|
||||||
* 跟踪 token 使用量和成本
|
* 跟踪 token 使用量和成本
|
||||||
*/
|
*/
|
||||||
async function applyRateLimitTracking(rateLimitInfo, usageSummary, model, context = '', keyId = null) {
|
async function applyRateLimitTracking(
|
||||||
|
rateLimitInfo,
|
||||||
|
usageSummary,
|
||||||
|
model,
|
||||||
|
context = '',
|
||||||
|
keyId = null
|
||||||
|
) {
|
||||||
if (!rateLimitInfo) {
|
if (!rateLimitInfo) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ class ApiKeyService {
|
|||||||
const hashedKey = this._hashApiKey(apiKey)
|
const hashedKey = this._hashApiKey(apiKey)
|
||||||
|
|
||||||
// 处理 permissions
|
// 处理 permissions
|
||||||
let permissionsValue = permissions
|
const permissionsValue = permissions
|
||||||
|
|
||||||
const keyData = {
|
const keyData = {
|
||||||
id: keyId,
|
id: keyId,
|
||||||
@@ -623,7 +623,9 @@ class ApiKeyService {
|
|||||||
|
|
||||||
// 统计 API Key 上的标签(trim 后统计)
|
// 统计 API Key 上的标签(trim 后统计)
|
||||||
for (const key of apiKeys) {
|
for (const key of apiKeys) {
|
||||||
if (key.isDeleted === 'true') continue
|
if (key.isDeleted === 'true') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
let tags = []
|
let tags = []
|
||||||
try {
|
try {
|
||||||
const parsed = key.tags ? JSON.parse(key.tags) : []
|
const parsed = key.tags ? JSON.parse(key.tags) : []
|
||||||
@@ -666,7 +668,9 @@ class ApiKeyService {
|
|||||||
let affectedCount = 0
|
let affectedCount = 0
|
||||||
|
|
||||||
for (const key of apiKeys) {
|
for (const key of apiKeys) {
|
||||||
if (key.isDeleted === 'true') continue
|
if (key.isDeleted === 'true') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
let tags = []
|
let tags = []
|
||||||
try {
|
try {
|
||||||
const parsed = key.tags ? JSON.parse(key.tags) : []
|
const parsed = key.tags ? JSON.parse(key.tags) : []
|
||||||
@@ -709,7 +713,9 @@ class ApiKeyService {
|
|||||||
let foundInKeys = false
|
let foundInKeys = false
|
||||||
|
|
||||||
for (const key of apiKeys) {
|
for (const key of apiKeys) {
|
||||||
if (key.isDeleted === 'true') continue
|
if (key.isDeleted === 'true') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
let tags = []
|
let tags = []
|
||||||
try {
|
try {
|
||||||
const parsed = key.tags ? JSON.parse(key.tags) : []
|
const parsed = key.tags ? JSON.parse(key.tags) : []
|
||||||
|
|||||||
@@ -211,7 +211,9 @@ class ServiceRatesService {
|
|||||||
* 根据账户类型获取服务类型(优先级高于模型推断)
|
* 根据账户类型获取服务类型(优先级高于模型推断)
|
||||||
*/
|
*/
|
||||||
getServiceFromAccountType(accountType) {
|
getServiceFromAccountType(accountType) {
|
||||||
if (!accountType) return null
|
if (!accountType) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const mapping = {
|
const mapping = {
|
||||||
claude: 'claude',
|
claude: 'claude',
|
||||||
|
|||||||
@@ -94,14 +94,23 @@ function mapToErrorCode(error, options = {}) {
|
|||||||
|
|
||||||
// 先按 HTTP 状态码快速匹配
|
// 先按 HTTP 状态码快速匹配
|
||||||
if (statusCode) {
|
if (statusCode) {
|
||||||
if (statusCode === 401) matchedCode = 'E003'
|
if (statusCode === 401) {
|
||||||
else if (statusCode === 403) matchedCode = 'E009'
|
matchedCode = 'E003'
|
||||||
else if (statusCode === 404) matchedCode = 'E010'
|
} else if (statusCode === 403) {
|
||||||
else if (statusCode === 429) matchedCode = 'E004'
|
matchedCode = 'E009'
|
||||||
else if (statusCode === 502) matchedCode = 'E007'
|
} else if (statusCode === 404) {
|
||||||
else if (statusCode === 503) matchedCode = 'E001'
|
matchedCode = 'E010'
|
||||||
else if (statusCode === 504) matchedCode = 'E008'
|
} else if (statusCode === 429) {
|
||||||
else if (statusCode === 529) matchedCode = 'E012'
|
matchedCode = 'E004'
|
||||||
|
} else if (statusCode === 502) {
|
||||||
|
matchedCode = 'E007'
|
||||||
|
} else if (statusCode === 503) {
|
||||||
|
matchedCode = 'E001'
|
||||||
|
} else if (statusCode === 504) {
|
||||||
|
matchedCode = 'E008'
|
||||||
|
} else if (statusCode === 529) {
|
||||||
|
matchedCode = 'E012'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 再按消息内容精确匹配(可能覆盖状态码匹配)
|
// 再按消息内容精确匹配(可能覆盖状态码匹配)
|
||||||
@@ -117,10 +126,15 @@ function mapToErrorCode(error, options = {}) {
|
|||||||
// 按错误 code 匹配(网络错误)
|
// 按错误 code 匹配(网络错误)
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
const codeStr = String(errorCode).toUpperCase()
|
const codeStr = String(errorCode).toUpperCase()
|
||||||
if (codeStr === 'ENOTFOUND' || codeStr === 'EAI_AGAIN') matchedCode = 'E002'
|
if (codeStr === 'ENOTFOUND' || codeStr === 'EAI_AGAIN') {
|
||||||
else if (codeStr === 'ECONNREFUSED' || codeStr === 'ECONNRESET') matchedCode = 'E002'
|
matchedCode = 'E002'
|
||||||
else if (codeStr === 'ETIMEDOUT' || codeStr === 'ESOCKETTIMEDOUT') matchedCode = 'E008'
|
} else if (codeStr === 'ECONNREFUSED' || codeStr === 'ECONNRESET') {
|
||||||
else if (codeStr === 'ECONNABORTED') matchedCode = 'E002'
|
matchedCode = 'E002'
|
||||||
|
} else if (codeStr === 'ETIMEDOUT' || codeStr === 'ESOCKETTIMEDOUT') {
|
||||||
|
matchedCode = 'E008'
|
||||||
|
} else if (codeStr === 'ECONNABORTED') {
|
||||||
|
matchedCode = 'E002'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = ERROR_CODES[matchedCode]
|
const result = ERROR_CODES[matchedCode]
|
||||||
@@ -135,12 +149,24 @@ function mapToErrorCode(error, options = {}) {
|
|||||||
* 提取原始错误消息
|
* 提取原始错误消息
|
||||||
*/
|
*/
|
||||||
function extractOriginalMessage(error) {
|
function extractOriginalMessage(error) {
|
||||||
if (!error) return ''
|
if (!error) {
|
||||||
if (typeof error === 'string') return error
|
return ''
|
||||||
if (error.message) return error.message
|
}
|
||||||
if (error.response?.data?.error?.message) return error.response.data.error.message
|
if (typeof error === 'string') {
|
||||||
if (error.response?.data?.error) return String(error.response.data.error)
|
return error
|
||||||
if (error.response?.data?.message) return error.response.data.message
|
}
|
||||||
|
if (error.message) {
|
||||||
|
return error.message
|
||||||
|
}
|
||||||
|
if (error.response?.data?.error?.message) {
|
||||||
|
return error.response.data.error.message
|
||||||
|
}
|
||||||
|
if (error.response?.data?.error) {
|
||||||
|
return String(error.response.data.error)
|
||||||
|
}
|
||||||
|
if (error.response?.data?.message) {
|
||||||
|
return error.response.data.message
|
||||||
|
}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +214,9 @@ function getSafeMessage(error, options = {}) {
|
|||||||
|
|
||||||
// 兼容旧接口
|
// 兼容旧接口
|
||||||
function sanitizeErrorMessage(message) {
|
function sanitizeErrorMessage(message) {
|
||||||
if (!message) return 'Service temporarily unavailable'
|
if (!message) {
|
||||||
|
return 'Service temporarily unavailable'
|
||||||
|
}
|
||||||
return mapToErrorCode({ message }, { logOriginal: false }).message
|
return mapToErrorCode({ message }, { logOriginal: false }).message
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,9 +229,13 @@ function extractErrorMessage(body) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isAccountDisabledError(statusCode, body) {
|
function isAccountDisabledError(statusCode, body) {
|
||||||
if (statusCode !== 400) return false
|
if (statusCode !== 400) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
const message = extractOriginalMessage(body)
|
const message = extractOriginalMessage(body)
|
||||||
if (!message) return false
|
if (!message) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
const lower = message.toLowerCase()
|
const lower = message.toLowerCase()
|
||||||
return (
|
return (
|
||||||
lower.includes('organization has been disabled') ||
|
lower.includes('organization has been disabled') ||
|
||||||
|
|||||||
@@ -8,7 +8,13 @@ function toNumber(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// keyId 和 accountType 用于计算倍率成本
|
// keyId 和 accountType 用于计算倍率成本
|
||||||
async function updateRateLimitCounters(rateLimitInfo, usageSummary, model, keyId = null, accountType = null) {
|
async function updateRateLimitCounters(
|
||||||
|
rateLimitInfo,
|
||||||
|
usageSummary,
|
||||||
|
model,
|
||||||
|
keyId = null,
|
||||||
|
accountType = null
|
||||||
|
) {
|
||||||
if (!rateLimitInfo) {
|
if (!rateLimitInfo) {
|
||||||
return { totalTokens: 0, totalCost: 0, ratedCost: 0 }
|
return { totalTokens: 0, totalCost: 0, ratedCost: 0 }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user