From 915544cc734dbc4894362bb4d06c535cc5cfa8b1 Mon Sep 17 00:00:00 2001 From: sczheng189 <724100151@qq.com> Date: Tue, 24 Feb 2026 16:59:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E8=B4=B9=E7=94=A8?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E9=80=BB=E8=BE=91=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E5=92=8C=E5=AD=98=E5=82=A8=E8=B4=B9=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/admin/apiKeys.js | 181 ++++++++++++++++----------------- src/routes/admin/usageStats.js | 147 +++++++++++++++++--------- src/routes/apiStats.js | 46 ++++++--- 3 files changed, 218 insertions(+), 156 deletions(-) diff --git a/src/routes/admin/apiKeys.js b/src/routes/admin/apiKeys.js index c374eb15..cff0fb65 100644 --- a/src/routes/admin/apiKeys.js +++ b/src/routes/admin/apiKeys.js @@ -1093,9 +1093,8 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { const currentMonth = `${tzDate.getUTCFullYear()}-${String(tzDate.getUTCMonth() + 1).padStart(2, '0')}` searchPatterns.push(`usage:${keyId}:model:monthly:*:${currentMonth}`) } else { - // all - 获取所有数据(日和月数据都查) - searchPatterns.push(`usage:${keyId}:model:daily:*`) - searchPatterns.push(`usage:${keyId}:model:monthly:*`) + // all - 使用 alltime key(无 TTL,数据完整),避免 daily/monthly 键过期导致数据丢失 + searchPatterns.push(`usage:${keyId}:model:alltime:*`) } // 使用 SCAN 收集所有匹配的 keys @@ -1109,7 +1108,7 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { } while (cursor !== '0') } - // 去重(避免日数据和月数据重复计算) + // 去重 const uniqueKeys = [...new Set(allKeys)] // 获取实时限制数据(窗口数据不受时间范围筛选影响,始终获取当前窗口状态) @@ -1128,7 +1127,6 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { const apiKey = await redis.getApiKey(keyId) const rateLimitWindow = parseInt(apiKey?.rateLimitWindow) || 0 const dailyCostLimit = parseFloat(apiKey?.dailyCostLimit) || 0 - const totalCostLimit = parseFloat(apiKey?.totalCostLimit) || 0 const weeklyOpusCostLimit = parseFloat(apiKey?.weeklyOpusCostLimit) || 0 // 只在启用了每日费用限制时查询 @@ -1136,11 +1134,9 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { dailyCost = await redis.getDailyCost(keyId) } - // 只在启用了总费用限制时查询 - if (totalCostLimit > 0) { - const totalCostKey = `usage:cost:total:${keyId}` - allTimeCost = parseFloat((await client.get(totalCostKey)) || '0') - } + // 始终查询 allTimeCost(用于展示和限额校验) + const totalCostKey = `usage:cost:total:${keyId}` + allTimeCost = parseFloat((await client.get(totalCostKey)) || '0') // 只在启用了 Claude 周费用限制时查询(字段名沿用 weeklyOpusCostLimit) if (weeklyOpusCostLimit > 0) { @@ -1149,7 +1145,7 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { weeklyOpusCost = await redis.getWeeklyOpusCost(keyId, resetDay, resetHour) } - // 只在启用了窗口限制时查询窗口数据(移到早期返回之前,确保窗口数据始终被获取) + // 只在启用了窗口限制时查询窗口数据 if (rateLimitWindow > 0) { const requestCountKey = `rate_limit:requests:${keyId}` const tokenCountKey = `rate_limit:tokens:${keyId}` @@ -1180,37 +1176,23 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { } } } - - // 🔧 FIX: 对于 "全部时间" 时间范围,直接使用 allTimeCost - // 因为 usage:*:model:daily:* 键有 30 天 TTL,旧数据已经过期 - if (timeRange === 'all' && allTimeCost > 0) { - logger.debug(`📊 使用 allTimeCost 计算 timeRange='all': ${allTimeCost}`) - - return { - requests: 0, // 旧数据详情不可用 - tokens: 0, - inputTokens: 0, - outputTokens: 0, - cacheCreateTokens: 0, - cacheReadTokens: 0, - cost: allTimeCost, - formattedCost: CostCalculator.formatCost(allTimeCost), - // 实时限制数据(始终返回,不受时间范围影响) - dailyCost, - weeklyOpusCost, - currentWindowCost, - currentWindowRequests, - currentWindowTokens, - windowRemainingSeconds, - windowStartTime, - windowEndTime, - allTimeCost - } - } } catch (error) { logger.warn(`⚠️ 获取实时限制数据失败 (key: ${keyId}):`, error.message) } + // 构建实时限制数据对象(各分支复用) + const limitData = { + dailyCost, + weeklyOpusCost, + currentWindowCost, + currentWindowRequests, + currentWindowTokens, + windowRemainingSeconds, + windowStartTime, + windowEndTime, + allTimeCost + } + // 如果没有使用数据,返回零值但包含窗口数据 if (uniqueKeys.length === 0) { return { @@ -1221,17 +1203,9 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { cacheCreateTokens: 0, cacheReadTokens: 0, cost: 0, + realCost: 0, formattedCost: '$0.00', - // 实时限制数据(始终返回,不受时间范围影响) - dailyCost, - weeklyOpusCost, - currentWindowCost, - currentWindowRequests, - currentWindowTokens, - windowRemainingSeconds, - windowStartTime, - windowEndTime, - allTimeCost + ...limitData } } @@ -1246,10 +1220,13 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { const modelStatsMap = new Map() let totalRequests = 0 + // alltime key 的模式:usage:{keyId}:model:alltime:{model} + const alltimeKeyPattern = /usage:.+:model:alltime:(.+)$/ // 用于去重:先统计月数据,避免与日数据重复 const dailyKeyPattern = /usage:.+:model:daily:(.+):\d{4}-\d{2}-\d{2}$/ const monthlyKeyPattern = /usage:.+:model:monthly:(.+):\d{4}-\d{2}$/ const currentMonth = `${tzDate.getUTCFullYear()}-${String(tzDate.getUTCMonth() + 1).padStart(2, '0')}` + const isAlltimeQuery = timeRange === 'all' for (let i = 0; i < results.length; i++) { const [err, data] = results[i] @@ -1262,27 +1239,37 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { let isMonthly = false // 提取模型名称 - const dailyMatch = key.match(dailyKeyPattern) - const monthlyMatch = key.match(monthlyKeyPattern) + if (isAlltimeQuery) { + const alltimeMatch = key.match(alltimeKeyPattern) + if (alltimeMatch) { + model = alltimeMatch[1] + } + } else { + const dailyMatch = key.match(dailyKeyPattern) + const monthlyMatch = key.match(monthlyKeyPattern) - if (dailyMatch) { - model = dailyMatch[1] - } else if (monthlyMatch) { - model = monthlyMatch[1] - isMonthly = true + if (dailyMatch) { + model = dailyMatch[1] + } else if (monthlyMatch) { + model = monthlyMatch[1] + isMonthly = true + } } if (!model) { continue } - // 跳过当前月的月数据 - if (isMonthly && key.includes(`:${currentMonth}`)) { - continue - } - // 跳过非当前月的日数据 - if (!isMonthly && !key.includes(`:${currentMonth}-`)) { - continue + // 日/月去重逻辑(alltime 不需要去重) + if (!isAlltimeQuery) { + // 跳过当前月的月数据(当前月用日数据更精确) + if (isMonthly && key.includes(`:${currentMonth}`)) { + continue + } + // 跳过非当前月的日数据(非当前月用月数据) + if (!isMonthly && !key.includes(`:${currentMonth}-`)) { + continue + } } if (!modelStatsMap.has(model)) { @@ -1293,7 +1280,10 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { cacheReadTokens: 0, ephemeral5mTokens: 0, ephemeral1hTokens: 0, - requests: 0 + requests: 0, + realCostMicro: 0, + ratedCostMicro: 0, + hasStoredCost: false }) } @@ -1310,11 +1300,19 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { parseInt(data.totalEphemeral1hTokens) || parseInt(data.ephemeral1hTokens) || 0 stats.requests += parseInt(data.totalRequests) || parseInt(data.requests) || 0 + // 累加已存储的费用(微美元) + if ('realCostMicro' in data || 'ratedCostMicro' in data) { + stats.realCostMicro += parseInt(data.realCostMicro) || 0 + stats.ratedCostMicro += parseInt(data.ratedCostMicro) || 0 + stats.hasStoredCost = true + } + totalRequests += parseInt(data.totalRequests) || parseInt(data.requests) || 0 } - // 计算费用 - let totalCost = 0 + // 汇总费用:优先使用已存储的费用,仅对无存储费用的旧数据 fallback 到 token 重算 + let totalRatedCost = 0 + let totalRealCost = 0 let inputTokens = 0 let outputTokens = 0 let cacheCreateTokens = 0 @@ -1326,23 +1324,30 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { cacheCreateTokens += stats.cacheCreateTokens cacheReadTokens += stats.cacheReadTokens - const costUsage = { - input_tokens: stats.inputTokens, - output_tokens: stats.outputTokens, - cache_creation_input_tokens: stats.cacheCreateTokens, - cache_read_input_tokens: stats.cacheReadTokens - } - - // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 - if (stats.ephemeral5mTokens > 0 || stats.ephemeral1hTokens > 0) { - costUsage.cache_creation = { - ephemeral_5m_input_tokens: stats.ephemeral5mTokens, - ephemeral_1h_input_tokens: stats.ephemeral1hTokens + if (stats.hasStoredCost) { + // 使用请求时已计算并存储的费用(精确,包含 1M 上下文、特殊计费等) + totalRatedCost += stats.ratedCostMicro / 1000000 + totalRealCost += stats.realCostMicro / 1000000 + } else { + // Legacy fallback:旧数据没有存储费用,从 token 重算(不精确但聊胜于无) + const costUsage = { + input_tokens: stats.inputTokens, + output_tokens: stats.outputTokens, + cache_creation_input_tokens: stats.cacheCreateTokens, + cache_read_input_tokens: stats.cacheReadTokens } - } - const costResult = CostCalculator.calculateCost(costUsage, model) - totalCost += costResult.costs.total + if (stats.ephemeral5mTokens > 0 || stats.ephemeral1hTokens > 0) { + costUsage.cache_creation = { + ephemeral_5m_input_tokens: stats.ephemeral5mTokens, + ephemeral_1h_input_tokens: stats.ephemeral1hTokens + } + } + + const costResult = CostCalculator.calculateCost(costUsage, model) + totalRatedCost += costResult.costs.total + totalRealCost += costResult.costs.total + } } const tokens = inputTokens + outputTokens + cacheCreateTokens + cacheReadTokens @@ -1354,18 +1359,10 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { outputTokens, cacheCreateTokens, cacheReadTokens, - cost: totalCost, - formattedCost: CostCalculator.formatCost(totalCost), - // 实时限制数据 - dailyCost, - weeklyOpusCost, - currentWindowCost, - currentWindowRequests, - currentWindowTokens, - windowRemainingSeconds, - windowStartTime, - windowEndTime, - allTimeCost // 历史总费用(用于总费用限制) + cost: totalRatedCost, + realCost: totalRealCost, + formattedCost: CostCalculator.formatCost(totalRatedCost), + ...limitData } } diff --git a/src/routes/admin/usageStats.js b/src/routes/admin/usageStats.js index c83aad4e..c8b5660b 100644 --- a/src/routes/admin/usageStats.js +++ b/src/routes/admin/usageStats.js @@ -1011,7 +1011,10 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) = cacheReadTokens: 0, ephemeral5mTokens: 0, ephemeral1hTokens: 0, - allTokens: 0 + allTokens: 0, + realCostMicro: 0, + ratedCostMicro: 0, + hasStoredCost: false }) } const stats = modelStatsMap.get(model) @@ -1023,6 +1026,11 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) = stats.ephemeral5mTokens += parseInt(data.ephemeral5mTokens) || 0 stats.ephemeral1hTokens += parseInt(data.ephemeral1hTokens) || 0 stats.allTokens += parseInt(data.allTokens) || 0 + if ('realCostMicro' in data || 'ratedCostMicro' in data) { + stats.realCostMicro += parseInt(data.realCostMicro) || 0 + stats.ratedCostMicro += parseInt(data.ratedCostMicro) || 0 + stats.hasStoredCost = true + } } } } else { @@ -1059,7 +1067,10 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) = cacheReadTokens: 0, ephemeral5mTokens: 0, ephemeral1hTokens: 0, - allTokens: 0 + allTokens: 0, + realCostMicro: 0, + ratedCostMicro: 0, + hasStoredCost: false }) } const stats = modelStatsMap.get(model) @@ -1071,6 +1082,11 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) = stats.ephemeral5mTokens += parseInt(data.ephemeral5mTokens) || 0 stats.ephemeral1hTokens += parseInt(data.ephemeral1hTokens) || 0 stats.allTokens += parseInt(data.allTokens) || 0 + if ('realCostMicro' in data || 'ratedCostMicro' in data) { + stats.realCostMicro += parseInt(data.realCostMicro) || 0 + stats.ratedCostMicro += parseInt(data.ratedCostMicro) || 0 + stats.hasStoredCost = true + } } } @@ -1078,23 +1094,36 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) = for (const [model, stats] of modelStatsMap) { logger.info(`📊 Model ${model} aggregated data:`, stats) - const usage = { - input_tokens: stats.inputTokens, - output_tokens: stats.outputTokens, - cache_creation_input_tokens: stats.cacheCreateTokens, - cache_read_input_tokens: stats.cacheReadTokens - } - - // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 - if (stats.ephemeral5mTokens > 0 || stats.ephemeral1hTokens > 0) { - usage.cache_creation = { - ephemeral_5m_input_tokens: stats.ephemeral5mTokens, - ephemeral_1h_input_tokens: stats.ephemeral1hTokens + let costData + if (stats.hasStoredCost) { + // 使用请求时已计算并存储的费用(精确,包含 1M 上下文、Fast Mode 等特殊计费) + const ratedCost = stats.ratedCostMicro / 1000000 + const realCost = stats.realCostMicro / 1000000 + costData = { + costs: { total: ratedCost, real: realCost }, + formatted: { total: CostCalculator.formatCost(ratedCost) }, + pricing: null, + usingDynamicPricing: false, + usingStoredCost: true + } + } else { + // Legacy fallback:旧数据没有存储费用,从 token 重算 + const usage = { + input_tokens: stats.inputTokens, + output_tokens: stats.outputTokens, + cache_creation_input_tokens: stats.cacheCreateTokens, + cache_read_input_tokens: stats.cacheReadTokens } - } - // 使用CostCalculator计算费用 - const costData = CostCalculator.calculateCost(usage, model) + if (stats.ephemeral5mTokens > 0 || stats.ephemeral1hTokens > 0) { + usage.cache_creation = { + ephemeral_5m_input_tokens: stats.ephemeral5mTokens, + ephemeral_1h_input_tokens: stats.ephemeral1hTokens + } + } + + costData = CostCalculator.calculateCost(usage, model) + } modelStats.push({ model, @@ -1933,26 +1962,37 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { continue } - const usage = { - input_tokens: parseInt(modelData.inputTokens) || 0, - output_tokens: parseInt(modelData.outputTokens) || 0, - cache_creation_input_tokens: parseInt(modelData.cacheCreateTokens) || 0, - cache_read_input_tokens: parseInt(modelData.cacheReadTokens) || 0 - } + // 优先使用已存储的费用 + const hasStoredCost = 'realCostMicro' in modelData || 'ratedCostMicro' in modelData + let modelCost = 0 - // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 - const eph5m = parseInt(modelData.ephemeral5mTokens) || 0 - const eph1h = parseInt(modelData.ephemeral1hTokens) || 0 - if (eph5m > 0 || eph1h > 0) { - usage.cache_creation = { - ephemeral_5m_input_tokens: eph5m, - ephemeral_1h_input_tokens: eph1h + if (hasStoredCost) { + modelCost = (parseInt(modelData.ratedCostMicro) || 0) / 1000000 + } else { + // Legacy fallback:旧数据没有存储费用,从 token 重算 + const usage = { + input_tokens: parseInt(modelData.inputTokens) || 0, + output_tokens: parseInt(modelData.outputTokens) || 0, + cache_creation_input_tokens: parseInt(modelData.cacheCreateTokens) || 0, + cache_read_input_tokens: parseInt(modelData.cacheReadTokens) || 0 } + + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + const eph5m = parseInt(modelData.ephemeral5mTokens) || 0 + const eph1h = parseInt(modelData.ephemeral1hTokens) || 0 + if (eph5m > 0 || eph1h > 0) { + usage.cache_creation = { + ephemeral_5m_input_tokens: eph5m, + ephemeral_1h_input_tokens: eph1h + } + } + + const costResult = CostCalculator.calculateCost(usage, model) + modelCost = costResult.costs.total } - const costResult = CostCalculator.calculateCost(usage, model) const currentCost = apiKeyCostMap.get(apiKeyId) || 0 - apiKeyCostMap.set(apiKeyId, currentCost + costResult.costs.total) + apiKeyCostMap.set(apiKeyId, currentCost + modelCost) } // 组合数据 @@ -2111,26 +2151,37 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { continue } - const usage = { - input_tokens: parseInt(modelData.inputTokens) || 0, - output_tokens: parseInt(modelData.outputTokens) || 0, - cache_creation_input_tokens: parseInt(modelData.cacheCreateTokens) || 0, - cache_read_input_tokens: parseInt(modelData.cacheReadTokens) || 0 - } + // 优先使用已存储的费用 + const hasStoredCost = 'realCostMicro' in modelData || 'ratedCostMicro' in modelData + let modelCost = 0 - // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 - const eph5m = parseInt(modelData.ephemeral5mTokens) || 0 - const eph1h = parseInt(modelData.ephemeral1hTokens) || 0 - if (eph5m > 0 || eph1h > 0) { - usage.cache_creation = { - ephemeral_5m_input_tokens: eph5m, - ephemeral_1h_input_tokens: eph1h + if (hasStoredCost) { + modelCost = (parseInt(modelData.ratedCostMicro) || 0) / 1000000 + } else { + // Legacy fallback:旧数据没有存储费用,从 token 重算 + const usage = { + input_tokens: parseInt(modelData.inputTokens) || 0, + output_tokens: parseInt(modelData.outputTokens) || 0, + cache_creation_input_tokens: parseInt(modelData.cacheCreateTokens) || 0, + cache_read_input_tokens: parseInt(modelData.cacheReadTokens) || 0 } + + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + const eph5m = parseInt(modelData.ephemeral5mTokens) || 0 + const eph1h = parseInt(modelData.ephemeral1hTokens) || 0 + if (eph5m > 0 || eph1h > 0) { + usage.cache_creation = { + ephemeral_5m_input_tokens: eph5m, + ephemeral_1h_input_tokens: eph1h + } + } + + const costResult = CostCalculator.calculateCost(usage, model) + modelCost = costResult.costs.total } - const costResult = CostCalculator.calculateCost(usage, model) const currentCost = apiKeyCostMap.get(apiKeyId) || 0 - apiKeyCostMap.set(apiKeyId, currentCost + costResult.costs.total) + apiKeyCostMap.set(apiKeyId, currentCost + modelCost) } // 组合数据 @@ -2628,7 +2679,7 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { } }) -// 获取 API Key 的请求记录时间线 +// 获取 API Key 的请求记��时间线 router.get('/api-keys/:keyId/usage-records', authenticateAdmin, async (req, res) => { try { const { keyId } = req.params diff --git a/src/routes/apiStats.js b/src/routes/apiStats.js index 83645892..776259cb 100644 --- a/src/routes/apiStats.js +++ b/src/routes/apiStats.js @@ -277,7 +277,10 @@ router.post('/api/user-stats', async (req, res) => { cacheCreateTokens: 0, cacheReadTokens: 0, ephemeral5mTokens: 0, - ephemeral1hTokens: 0 + ephemeral1hTokens: 0, + realCostMicro: 0, + ratedCostMicro: 0, + hasStoredCost: false }) } @@ -288,28 +291,39 @@ router.post('/api/user-stats', async (req, res) => { modelUsage.cacheReadTokens += parseInt(data.cacheReadTokens) || 0 modelUsage.ephemeral5mTokens += parseInt(data.ephemeral5mTokens) || 0 modelUsage.ephemeral1hTokens += parseInt(data.ephemeral1hTokens) || 0 + if ('realCostMicro' in data || 'ratedCostMicro' in data) { + modelUsage.realCostMicro += parseInt(data.realCostMicro) || 0 + modelUsage.ratedCostMicro += parseInt(data.ratedCostMicro) || 0 + modelUsage.hasStoredCost = true + } } } // 按模型计算费用并汇总 for (const [model, usage] of modelUsageMap) { - const usageData = { - input_tokens: usage.inputTokens, - output_tokens: usage.outputTokens, - cache_creation_input_tokens: usage.cacheCreateTokens, - cache_read_input_tokens: usage.cacheReadTokens - } - - // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 - if (usage.ephemeral5mTokens > 0 || usage.ephemeral1hTokens > 0) { - usageData.cache_creation = { - ephemeral_5m_input_tokens: usage.ephemeral5mTokens, - ephemeral_1h_input_tokens: usage.ephemeral1hTokens + if (usage.hasStoredCost) { + // 使用请求时已存储的费用(精确) + totalCost += usage.ratedCostMicro / 1000000 + } else { + // Legacy fallback:旧数据没有存储费用,从 token 重算 + const usageData = { + input_tokens: usage.inputTokens, + output_tokens: usage.outputTokens, + cache_creation_input_tokens: usage.cacheCreateTokens, + cache_read_input_tokens: usage.cacheReadTokens } - } - const costResult = CostCalculator.calculateCost(usageData, model) - totalCost += costResult.costs.total + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + if (usage.ephemeral5mTokens > 0 || usage.ephemeral1hTokens > 0) { + usageData.cache_creation = { + ephemeral_5m_input_tokens: usage.ephemeral5mTokens, + ephemeral_1h_input_tokens: usage.ephemeral1hTokens + } + } + + const costResult = CostCalculator.calculateCost(usageData, model) + totalCost += costResult.costs.total + } } // 如果没有模型级别的详细数据,回退到总体数据计算