diff --git a/src/models/redis.js b/src/models/redis.js index dbac2a7d..bdffc9c6 100644 --- a/src/models/redis.js +++ b/src/models/redis.js @@ -1286,6 +1286,8 @@ class RedisClient { outputTokens = 0, cacheCreateTokens = 0, cacheReadTokens = 0, + ephemeral5mTokens = 0, + ephemeral1hTokens = 0, model = 'unknown', isLongContextRequest = false ) { @@ -1317,6 +1319,8 @@ class RedisClient { const finalOutputTokens = outputTokens || 0 const finalCacheCreateTokens = cacheCreateTokens || 0 const finalCacheReadTokens = cacheReadTokens || 0 + const finalEphemeral5mTokens = ephemeral5mTokens || 0 + const finalEphemeral1hTokens = ephemeral1hTokens || 0 const actualTotalTokens = finalInputTokens + finalOutputTokens + finalCacheCreateTokens + finalCacheReadTokens const coreTokens = finalInputTokens + finalOutputTokens @@ -1329,6 +1333,8 @@ class RedisClient { this.client.hincrby(accountKey, 'totalOutputTokens', finalOutputTokens), this.client.hincrby(accountKey, 'totalCacheCreateTokens', finalCacheCreateTokens), this.client.hincrby(accountKey, 'totalCacheReadTokens', finalCacheReadTokens), + this.client.hincrby(accountKey, 'totalEphemeral5mTokens', finalEphemeral5mTokens), + this.client.hincrby(accountKey, 'totalEphemeral1hTokens', finalEphemeral1hTokens), this.client.hincrby(accountKey, 'totalAllTokens', actualTotalTokens), this.client.hincrby(accountKey, 'totalRequests', 1), @@ -1338,6 +1344,8 @@ class RedisClient { this.client.hincrby(accountDaily, 'outputTokens', finalOutputTokens), this.client.hincrby(accountDaily, 'cacheCreateTokens', finalCacheCreateTokens), this.client.hincrby(accountDaily, 'cacheReadTokens', finalCacheReadTokens), + this.client.hincrby(accountDaily, 'ephemeral5mTokens', finalEphemeral5mTokens), + this.client.hincrby(accountDaily, 'ephemeral1hTokens', finalEphemeral1hTokens), this.client.hincrby(accountDaily, 'allTokens', actualTotalTokens), this.client.hincrby(accountDaily, 'requests', 1), @@ -1347,6 +1355,8 @@ class RedisClient { this.client.hincrby(accountMonthly, 'outputTokens', finalOutputTokens), this.client.hincrby(accountMonthly, 'cacheCreateTokens', finalCacheCreateTokens), this.client.hincrby(accountMonthly, 'cacheReadTokens', finalCacheReadTokens), + this.client.hincrby(accountMonthly, 'ephemeral5mTokens', finalEphemeral5mTokens), + this.client.hincrby(accountMonthly, 'ephemeral1hTokens', finalEphemeral1hTokens), this.client.hincrby(accountMonthly, 'allTokens', actualTotalTokens), this.client.hincrby(accountMonthly, 'requests', 1), @@ -1356,6 +1366,8 @@ class RedisClient { this.client.hincrby(accountHourly, 'outputTokens', finalOutputTokens), this.client.hincrby(accountHourly, 'cacheCreateTokens', finalCacheCreateTokens), this.client.hincrby(accountHourly, 'cacheReadTokens', finalCacheReadTokens), + this.client.hincrby(accountHourly, 'ephemeral5mTokens', finalEphemeral5mTokens), + this.client.hincrby(accountHourly, 'ephemeral1hTokens', finalEphemeral1hTokens), this.client.hincrby(accountHourly, 'allTokens', actualTotalTokens), this.client.hincrby(accountHourly, 'requests', 1), @@ -1376,6 +1388,16 @@ class RedisClient { `model:${normalizedModel}:cacheReadTokens`, finalCacheReadTokens ), + this.client.hincrby( + accountHourly, + `model:${normalizedModel}:ephemeral5mTokens`, + finalEphemeral5mTokens + ), + this.client.hincrby( + accountHourly, + `model:${normalizedModel}:ephemeral1hTokens`, + finalEphemeral1hTokens + ), this.client.hincrby(accountHourly, `model:${normalizedModel}:allTokens`, actualTotalTokens), this.client.hincrby(accountHourly, `model:${normalizedModel}:requests`, 1), @@ -1384,6 +1406,8 @@ class RedisClient { this.client.hincrby(accountModelDaily, 'outputTokens', finalOutputTokens), this.client.hincrby(accountModelDaily, 'cacheCreateTokens', finalCacheCreateTokens), this.client.hincrby(accountModelDaily, 'cacheReadTokens', finalCacheReadTokens), + this.client.hincrby(accountModelDaily, 'ephemeral5mTokens', finalEphemeral5mTokens), + this.client.hincrby(accountModelDaily, 'ephemeral1hTokens', finalEphemeral1hTokens), this.client.hincrby(accountModelDaily, 'allTokens', actualTotalTokens), this.client.hincrby(accountModelDaily, 'requests', 1), @@ -1392,6 +1416,8 @@ class RedisClient { this.client.hincrby(accountModelMonthly, 'outputTokens', finalOutputTokens), this.client.hincrby(accountModelMonthly, 'cacheCreateTokens', finalCacheCreateTokens), this.client.hincrby(accountModelMonthly, 'cacheReadTokens', finalCacheReadTokens), + this.client.hincrby(accountModelMonthly, 'ephemeral5mTokens', finalEphemeral5mTokens), + this.client.hincrby(accountModelMonthly, 'ephemeral1hTokens', finalEphemeral1hTokens), this.client.hincrby(accountModelMonthly, 'allTokens', actualTotalTokens), this.client.hincrby(accountModelMonthly, 'requests', 1), @@ -1400,6 +1426,8 @@ class RedisClient { this.client.hincrby(accountModelHourly, 'outputTokens', finalOutputTokens), this.client.hincrby(accountModelHourly, 'cacheCreateTokens', finalCacheCreateTokens), this.client.hincrby(accountModelHourly, 'cacheReadTokens', finalCacheReadTokens), + this.client.hincrby(accountModelHourly, 'ephemeral5mTokens', finalEphemeral5mTokens), + this.client.hincrby(accountModelHourly, 'ephemeral1hTokens', finalEphemeral1hTokens), this.client.hincrby(accountModelHourly, 'allTokens', actualTotalTokens), this.client.hincrby(accountModelHourly, 'requests', 1), @@ -1867,6 +1895,16 @@ class RedisClient { cache_read_input_tokens: parseInt(modelUsage.cacheReadTokens || 0) } + // 添加 cache_creation 子对象以支持精确 ephemeral 定价 + const eph5m = parseInt(modelUsage.ephemeral5mTokens) || 0 + const eph1h = parseInt(modelUsage.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) totalCost += costResult.costs.total @@ -1955,6 +1993,16 @@ class RedisClient { cache_read_input_tokens: parseInt(modelUsage.cacheReadTokens || 0) } + // 添加 cache_creation 子对象以支持精确 ephemeral 定价 + const eph5m = parseInt(modelUsage.ephemeral5mTokens) || 0 + const eph1h = parseInt(modelUsage.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) costMap.set(accountId, costMap.get(accountId) + costResult.costs.total) } @@ -1996,6 +2044,17 @@ class RedisClient { cache_creation_input_tokens: parseInt(modelUsage.cacheCreateTokens || 0), cache_read_input_tokens: parseInt(modelUsage.cacheReadTokens || 0) } + + // 添加 cache_creation 子对象以支持精确 ephemeral 定价 + const eph5m = parseInt(modelUsage.ephemeral5mTokens) || 0 + const eph1h = parseInt(modelUsage.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) totalCost += costResult.costs.total } @@ -3646,6 +3705,8 @@ class RedisClient { outputTokens: 0, cacheCreateTokens: 0, cacheReadTokens: 0, + ephemeral5mTokens: 0, + ephemeral1hTokens: 0, allTokens: 0, requests: 0 } @@ -3659,6 +3720,10 @@ class RedisClient { modelUsage[modelName].cacheCreateTokens += parseInt(value || 0) } else if (metric === 'cacheReadTokens') { modelUsage[modelName].cacheReadTokens += parseInt(value || 0) + } else if (metric === 'ephemeral5mTokens') { + modelUsage[modelName].ephemeral5mTokens += parseInt(value || 0) + } else if (metric === 'ephemeral1hTokens') { + modelUsage[modelName].ephemeral1hTokens += parseInt(value || 0) } else if (metric === 'allTokens') { modelUsage[modelName].allTokens += parseInt(value || 0) } else if (metric === 'requests') { diff --git a/src/routes/admin/claudeAccounts.js b/src/routes/admin/claudeAccounts.js index 590e919d..699aa3ac 100644 --- a/src/routes/admin/claudeAccounts.js +++ b/src/routes/admin/claudeAccounts.js @@ -417,6 +417,14 @@ router.get('/claude-accounts', authenticateAdmin, async (req, res) => { cache_read_input_tokens: usage.cacheReadTokens } + // 添加 cache_creation 子对象以支持精确 ephemeral 定价 + if (usage.ephemeral5mTokens > 0 || usage.ephemeral1hTokens > 0) { + usageData.cache_creation = { + ephemeral_5m_input_tokens: usage.ephemeral5mTokens, + ephemeral_1h_input_tokens: usage.ephemeral1hTokens + } + } + logger.debug(`💰 Calculating cost for model ${modelName}:`, JSON.stringify(usageData)) const costResult = CostCalculator.calculateCost(usageData, modelName) logger.debug(`💰 Cost result for ${modelName}: total=${costResult.costs.total}`) diff --git a/src/routes/admin/usageStats.js b/src/routes/admin/usageStats.js index 5bd9ea79..849ad54b 100644 --- a/src/routes/admin/usageStats.js +++ b/src/routes/admin/usageStats.js @@ -362,6 +362,16 @@ router.get('/accounts/:accountId/usage-history', authenticateAdmin, async (req, 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, modelName) summedCost += costResult.costs.total } @@ -403,6 +413,15 @@ router.get('/accounts/:accountId/usage-history', authenticateAdmin, async (req, cache_creation_input_tokens: cacheCreateTokens, cache_read_input_tokens: cacheReadTokens } + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + const fbEph5m = parseInt(dailyData?.ephemeral5mTokens) || 0 + const fbEph1h = parseInt(dailyData?.ephemeral1hTokens) || 0 + if (fbEph5m > 0 || fbEph1h > 0) { + fallbackUsage.cache_creation = { + ephemeral_5m_input_tokens: fbEph5m, + ephemeral_1h_input_tokens: fbEph1h + } + } const fallbackResult = CostCalculator.calculateCost(fallbackUsage, fallbackModel) cost = fallbackResult.costs.total } @@ -653,12 +672,23 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { cache_creation_input_tokens: modelCacheCreateTokens, cache_read_input_tokens: modelCacheReadTokens } + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + const mEph5m = parseInt(data.ephemeral5mTokens) || 0 + const mEph1h = parseInt(data.ephemeral1hTokens) || 0 + if (mEph5m > 0 || mEph1h > 0) { + modelUsage.cache_creation = { + ephemeral_5m_input_tokens: mEph5m, + ephemeral_1h_input_tokens: mEph1h + } + } const modelCostResult = CostCalculator.calculateCost(modelUsage, model) hourCost += modelCostResult.costs.total } // 如果没有模型级别的数据,尝试API Key级别的数据 if (modelKeys.length === 0) { + let hourEph5m = 0 + let hourEph1h = 0 for (const key of usageKeys) { const data = usageDataMap.get(key) if (data) { @@ -667,6 +697,8 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { hourRequests += parseInt(data.requests) || 0 hourCacheCreateTokens += parseInt(data.cacheCreateTokens) || 0 hourCacheReadTokens += parseInt(data.cacheReadTokens) || 0 + hourEph5m += parseInt(data.ephemeral5mTokens) || 0 + hourEph1h += parseInt(data.ephemeral1hTokens) || 0 } } @@ -676,6 +708,13 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { cache_creation_input_tokens: hourCacheCreateTokens, cache_read_input_tokens: hourCacheReadTokens } + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + if (hourEph5m > 0 || hourEph1h > 0) { + usage.cache_creation = { + ephemeral_5m_input_tokens: hourEph5m, + ephemeral_1h_input_tokens: hourEph1h + } + } const costResult = CostCalculator.calculateCost(usage, 'unknown') hourCost = costResult.costs.total } @@ -817,6 +856,8 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { // 如果没有模型级别的数据,回退到原始方法 if (modelKeys.length === 0 && usageKeys.length > 0) { + let dayEph5m = 0 + let dayEph1h = 0 for (const key of usageKeys) { const data = usageDataMap.get(key) if (data) { @@ -825,6 +866,8 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { dayRequests += parseInt(data.requests) || 0 dayCacheCreateTokens += parseInt(data.cacheCreateTokens) || 0 dayCacheReadTokens += parseInt(data.cacheReadTokens) || 0 + dayEph5m += parseInt(data.ephemeral5mTokens) || 0 + dayEph1h += parseInt(data.ephemeral1hTokens) || 0 } } @@ -834,6 +877,13 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { cache_creation_input_tokens: dayCacheCreateTokens, cache_read_input_tokens: dayCacheReadTokens } + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + if (dayEph5m > 0 || dayEph1h > 0) { + usage.cache_creation = { + ephemeral_5m_input_tokens: dayEph5m, + ephemeral_1h_input_tokens: dayEph1h + } + } const costResult = CostCalculator.calculateCost(usage, 'unknown') dayCost = costResult.costs.total } @@ -1097,6 +1147,16 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) = cache_read_input_tokens: usageData.cacheReadTokens || 0 } + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + const histEph5m = usageData.ephemeral5mTokens || 0 + const histEph1h = usageData.ephemeral1hTokens || 0 + if (histEph5m > 0 || histEph1h > 0) { + usage.cache_creation = { + ephemeral_5m_input_tokens: histEph5m, + ephemeral_1h_input_tokens: histEph1h + } + } + // 对于汇总数据,使用默认模型计算费用 const costData = CostCalculator.calculateCost(usage, 'claude-3-5-sonnet-20241022') @@ -1472,6 +1532,15 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => { cache_creation_input_tokens: cacheCreateTokens, cache_read_input_tokens: cacheReadTokens } + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + const fbEph5m = parseInt(data.ephemeral5mTokens) || 0 + const fbEph1h = parseInt(data.ephemeral1hTokens) || 0 + if (fbEph5m > 0 || fbEph1h > 0) { + fallbackUsage.cache_creation = { + ephemeral_5m_input_tokens: fbEph5m, + ephemeral_1h_input_tokens: fbEph1h + } + } const fallbackResult = CostCalculator.calculateCost(fallbackUsage, fallbackModel) cost = fallbackResult.costs.total } @@ -1640,6 +1709,15 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => { cache_creation_input_tokens: cacheCreateTokens, cache_read_input_tokens: cacheReadTokens } + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + const fbEph5m = parseInt(data.ephemeral5mTokens) || 0 + const fbEph1h = parseInt(data.ephemeral1hTokens) || 0 + if (fbEph5m > 0 || fbEph1h > 0) { + fallbackUsage.cache_creation = { + ephemeral_5m_input_tokens: fbEph5m, + ephemeral_1h_input_tokens: fbEph1h + } + } const fallbackResult = CostCalculator.calculateCost(fallbackUsage, fallbackModel) cost = fallbackResult.costs.total } @@ -1834,7 +1912,9 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { inputTokens, outputTokens, cacheCreateTokens, - cacheReadTokens + cacheReadTokens, + ephemeral5mTokens: parseInt(data.ephemeral5mTokens) || 0, + ephemeral1hTokens: parseInt(data.ephemeral1hTokens) || 0 }) } @@ -1860,6 +1940,16 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { 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) const currentCost = apiKeyCostMap.get(apiKeyId) || 0 apiKeyCostMap.set(apiKeyId, currentCost + costResult.costs.total) @@ -1878,6 +1968,12 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { cache_creation_input_tokens: data.cacheCreateTokens, cache_read_input_tokens: data.cacheReadTokens } + if (data.ephemeral5mTokens > 0 || data.ephemeral1hTokens > 0) { + usage.cache_creation = { + ephemeral_5m_input_tokens: data.ephemeral5mTokens, + ephemeral_1h_input_tokens: data.ephemeral1hTokens + } + } const fallbackResult = CostCalculator.calculateCost(usage, 'claude-3-5-sonnet-20241022') cost = fallbackResult.costs.total formattedCost = fallbackResult.formatted.total @@ -1994,7 +2090,9 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { inputTokens, outputTokens, cacheCreateTokens, - cacheReadTokens + cacheReadTokens, + ephemeral5mTokens: parseInt(data.ephemeral5mTokens) || 0, + ephemeral1hTokens: parseInt(data.ephemeral1hTokens) || 0 }) } @@ -2020,6 +2118,16 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { 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) const currentCost = apiKeyCostMap.get(apiKeyId) || 0 apiKeyCostMap.set(apiKeyId, currentCost + costResult.costs.total) @@ -2038,6 +2146,12 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { cache_creation_input_tokens: data.cacheCreateTokens, cache_read_input_tokens: data.cacheReadTokens } + if (data.ephemeral5mTokens > 0 || data.ephemeral1hTokens > 0) { + usage.cache_creation = { + ephemeral_5m_input_tokens: data.ephemeral5mTokens, + ephemeral_1h_input_tokens: data.ephemeral1hTokens + } + } const fallbackResult = CostCalculator.calculateCost(usage, 'claude-3-5-sonnet-20241022') cost = fallbackResult.costs.total formattedCost = fallbackResult.formatted.total @@ -2189,7 +2303,9 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { inputTokens: 0, outputTokens: 0, cacheCreateTokens: 0, - cacheReadTokens: 0 + cacheReadTokens: 0, + ephemeral5mTokens: 0, + ephemeral1hTokens: 0 }) } @@ -2198,6 +2314,8 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { modelUsage.outputTokens += parseInt(data.outputTokens) || 0 modelUsage.cacheCreateTokens += parseInt(data.cacheCreateTokens) || 0 modelUsage.cacheReadTokens += parseInt(data.cacheReadTokens) || 0 + modelUsage.ephemeral5mTokens += parseInt(data.ephemeral5mTokens) || 0 + modelUsage.ephemeral1hTokens += parseInt(data.ephemeral1hTokens) || 0 } // 计算7天统计的费用 @@ -2211,6 +2329,14 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { 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 + } + } + const costResult = CostCalculator.calculateCost(usageData, model) totalCosts.inputCost += costResult.costs.input totalCosts.outputCost += costResult.costs.output @@ -2290,7 +2416,9 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { inputTokens: 0, outputTokens: 0, cacheCreateTokens: 0, - cacheReadTokens: 0 + cacheReadTokens: 0, + ephemeral5mTokens: 0, + ephemeral1hTokens: 0 }) } @@ -2299,6 +2427,8 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { modelUsage.outputTokens += parseInt(data.outputTokens) || 0 modelUsage.cacheCreateTokens += parseInt(data.cacheCreateTokens) || 0 modelUsage.cacheReadTokens += parseInt(data.cacheReadTokens) || 0 + modelUsage.ephemeral5mTokens += parseInt(data.ephemeral5mTokens) || 0 + modelUsage.ephemeral1hTokens += parseInt(data.ephemeral1hTokens) || 0 } // 使用模型级别的数据计算费用 @@ -2312,6 +2442,14 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { 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 + } + } + const costResult = CostCalculator.calculateCost(usageData, model) totalCosts.inputCost += costResult.costs.input totalCosts.outputCost += costResult.costs.output @@ -2352,6 +2490,16 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { cache_read_input_tokens: apiKey.usage.total.cacheReadTokens || 0 } + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + const totalEph5m = apiKey.usage.total.ephemeral5mTokens || 0 + const totalEph1h = apiKey.usage.total.ephemeral1hTokens || 0 + if (totalEph5m > 0 || totalEph1h > 0) { + usage.cache_creation = { + ephemeral_5m_input_tokens: totalEph5m, + ephemeral_1h_input_tokens: totalEph1h + } + } + // 使用加权平均价格计算(基于当前活跃模型的价格分布) const costResult = CostCalculator.calculateCost(usage, 'claude-3-5-haiku-20241022') totalCosts.inputCost += costResult.costs.input @@ -2424,6 +2572,16 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { cache_read_input_tokens: parseInt(data.cacheReadTokens) || 0 } + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + const eph5m = parseInt(data.ephemeral5mTokens) || 0 + const eph1h = parseInt(data.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) // 累加总费用 @@ -2564,13 +2722,27 @@ router.get('/api-keys/:keyId/usage-records', authenticateAdmin, async (req, res) return null } - const toUsageObject = (record) => ({ - input_tokens: record.inputTokens || 0, - output_tokens: record.outputTokens || 0, - cache_creation_input_tokens: record.cacheCreateTokens || 0, - cache_read_input_tokens: record.cacheReadTokens || 0, - cache_creation: record.cacheCreation || record.cache_creation || null - }) + const toUsageObject = (record) => { + const usage = { + input_tokens: record.inputTokens || 0, + output_tokens: record.outputTokens || 0, + cache_creation_input_tokens: record.cacheCreateTokens || 0, + cache_read_input_tokens: record.cacheReadTokens || 0, + cache_creation: record.cacheCreation || record.cache_creation || null + } + // 如果没有 cache_creation 但有独立存储的 ephemeral 字段,构建子对象 + if (!usage.cache_creation) { + const eph5m = parseInt(record.ephemeral5mTokens) || 0 + const eph1h = parseInt(record.ephemeral1hTokens) || 0 + if (eph5m > 0 || eph1h > 0) { + usage.cache_creation = { + ephemeral_5m_input_tokens: eph5m, + ephemeral_1h_input_tokens: eph1h + } + } + } + return usage + } const withinRange = (record) => { if (!record.timestamp) { @@ -2863,13 +3035,27 @@ router.get('/accounts/:accountId/usage-records', authenticateAdmin, async (req, keysToUse = [{ id: apiKeyId }] } - const toUsageObject = (record) => ({ - input_tokens: record.inputTokens || 0, - output_tokens: record.outputTokens || 0, - cache_creation_input_tokens: record.cacheCreateTokens || 0, - cache_read_input_tokens: record.cacheReadTokens || 0, - cache_creation: record.cacheCreation || record.cache_creation || null - }) + const toUsageObject = (record) => { + const usage = { + input_tokens: record.inputTokens || 0, + output_tokens: record.outputTokens || 0, + cache_creation_input_tokens: record.cacheCreateTokens || 0, + cache_read_input_tokens: record.cacheReadTokens || 0, + cache_creation: record.cacheCreation || record.cache_creation || null + } + // 如果没有 cache_creation 但有独立存储的 ephemeral 字段,构建子对象 + if (!usage.cache_creation) { + const eph5m = parseInt(record.ephemeral5mTokens) || 0 + const eph1h = parseInt(record.ephemeral1hTokens) || 0 + if (eph5m > 0 || eph1h > 0) { + usage.cache_creation = { + ephemeral_5m_input_tokens: eph5m, + ephemeral_1h_input_tokens: eph1h + } + } + } + return usage + } const withinRange = (record) => { if (!record.timestamp) { diff --git a/src/routes/apiStats.js b/src/routes/apiStats.js index a156df65..86f1738b 100644 --- a/src/routes/apiStats.js +++ b/src/routes/apiStats.js @@ -317,6 +317,14 @@ router.post('/api/user-stats', async (req, res) => { cache_read_input_tokens: usage.cacheReadTokens || 0 } + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + if (usage.ephemeral5mTokens > 0 || usage.ephemeral1hTokens > 0) { + costUsage.cache_creation = { + ephemeral_5m_input_tokens: usage.ephemeral5mTokens, + ephemeral_1h_input_tokens: usage.ephemeral1hTokens + } + } + const costResult = CostCalculator.calculateCost(costUsage, 'claude-3-5-sonnet-20241022') totalCost = costResult.costs.total } @@ -335,6 +343,14 @@ router.post('/api/user-stats', async (req, res) => { cache_read_input_tokens: usage.cacheReadTokens || 0 } + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + if (usage.ephemeral5mTokens > 0 || usage.ephemeral1hTokens > 0) { + costUsage.cache_creation = { + ephemeral_5m_input_tokens: usage.ephemeral5mTokens, + ephemeral_1h_input_tokens: usage.ephemeral1hTokens + } + } + const costResult = CostCalculator.calculateCost(costUsage, 'claude-3-5-sonnet-20241022') totalCost = costResult.costs.total formattedCost = costResult.formatted.total @@ -804,6 +820,8 @@ router.post('/api/batch-model-stats', async (req, res) => { outputTokens: 0, cacheCreateTokens: 0, cacheReadTokens: 0, + ephemeral5mTokens: 0, + ephemeral1hTokens: 0, allTokens: 0, realCostMicro: 0, ratedCostMicro: 0, @@ -817,6 +835,8 @@ router.post('/api/batch-model-stats', async (req, res) => { modelUsage.outputTokens += parseInt(data.outputTokens) || 0 modelUsage.cacheCreateTokens += parseInt(data.cacheCreateTokens) || 0 modelUsage.cacheReadTokens += parseInt(data.cacheReadTokens) || 0 + modelUsage.ephemeral5mTokens += parseInt(data.ephemeral5mTokens) || 0 + modelUsage.ephemeral1hTokens += parseInt(data.ephemeral1hTokens) || 0 modelUsage.allTokens += parseInt(data.allTokens) || 0 modelUsage.realCostMicro += parseInt(data.realCostMicro) || 0 modelUsage.ratedCostMicro += parseInt(data.ratedCostMicro) || 0 @@ -839,6 +859,14 @@ router.post('/api/batch-model-stats', async (req, res) => { 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 + } + } + // 优先使用存储的费用,否则回退到重新计算 const { hasStoredCost } = usage const costData = CostCalculator.calculateCost(usageData, model) @@ -1368,6 +1396,8 @@ router.post('/api/user-model-stats', async (req, res) => { const model = match[1] if (data && Object.keys(data).length > 0) { + const ephemeral5m = parseInt(data.ephemeral5mTokens) || 0 + const ephemeral1h = parseInt(data.ephemeral1hTokens) || 0 const usage = { input_tokens: parseInt(data.inputTokens) || 0, output_tokens: parseInt(data.outputTokens) || 0, @@ -1375,6 +1405,14 @@ router.post('/api/user-model-stats', async (req, res) => { cache_read_input_tokens: parseInt(data.cacheReadTokens) || 0 } + // 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费 + if (ephemeral5m > 0 || ephemeral1h > 0) { + usage.cache_creation = { + ephemeral_5m_input_tokens: ephemeral5m, + ephemeral_1h_input_tokens: ephemeral1h + } + } + // 优先使用存储的费用,否则回退到重新计算 // 检查字段是否存在(而非 > 0),以支持真正的零成本场景 const realCostMicro = parseInt(data.realCostMicro) || 0 diff --git a/src/services/apiKeyService.js b/src/services/apiKeyService.js index 0ca80b9f..605707aa 100644 --- a/src/services/apiKeyService.js +++ b/src/services/apiKeyService.js @@ -1599,6 +1599,8 @@ class ApiKeyService { outputTokens, cacheCreateTokens, cacheReadTokens, + 0, // ephemeral5mTokens - recordUsage 不含详细缓存数据 + 0, // ephemeral1hTokens - recordUsage 不含详细缓存数据 model, isLongContextRequest ) @@ -1834,6 +1836,8 @@ class ApiKeyService { outputTokens, cacheCreateTokens, cacheReadTokens, + ephemeral5mTokens, + ephemeral1hTokens, model, costInfo.isLongContextRequest || false ) diff --git a/src/services/costInitService.js b/src/services/costInitService.js index 5463871f..3207f996 100644 --- a/src/services/costInitService.js +++ b/src/services/costInitService.js @@ -201,6 +201,16 @@ class CostInitService { parseInt(data.totalCacheReadTokens) || parseInt(data.cacheReadTokens) || 0 } + // 添加 cache_creation 子对象以支持精确 ephemeral 定价 + const eph5m = parseInt(data.ephemeral5mTokens) || 0 + const eph1h = parseInt(data.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) const cost = costResult.costs.total diff --git a/src/services/droidRelayService.js b/src/services/droidRelayService.js index 8e663611..0ed4e37e 100644 --- a/src/services/droidRelayService.js +++ b/src/services/droidRelayService.js @@ -1275,6 +1275,8 @@ class DroidRelayService { usageObject.output_tokens || 0, usageObject.cache_creation_input_tokens || 0, usageObject.cache_read_input_tokens || 0, + 0, // ephemeral5mTokens - Droid 不含详细缓存数据 + 0, // ephemeral1hTokens - Droid 不含详细缓存数据 model, false )