From e54611034483091af0dcf51c5a07f66f97bc40fb Mon Sep 17 00:00:00 2001 From: Pawel Date: Mon, 6 Apr 2026 22:03:47 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B9=20=D1=82=D1=80=D0=B5=D0=BA=D0=B8=D0=BD?= =?UTF-8?q?=D0=B3=20=D0=BA=D1=8D=D1=88-=D1=82=D0=BE=D0=BA=D0=B5=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B8=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D1=82=D0=B0=20=D0=B2=20computeSessionCost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Кэш-токены (cache_read, cache_create) трекаются отдельно от input - Добавлен расчёт утилизации контекстного окна (средний % по всем ходам, база 200K) - getCostAnalytics расширен: totalInputTokens, totalOutputTokens, totalCacheReadTokens, totalCacheCreateTokens, avgContextPct, dailyRate, byAgent, agentNoCostData - totalSessions теперь считает только сессии с реальными данными - Обратно совместимо: новые поля добавлены, старые не изменены Co-Authored-By: Claude Opus 4.6 --- src/data.js | 85 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/src/data.js b/src/data.js index 29847b4..e423118 100644 --- a/src/data.js +++ b/src/data.js @@ -1300,6 +1300,8 @@ function getSessionReplay(sessionId, project) { }; } +const CONTEXT_WINDOW = 200_000; // Claude's max context window (tokens) + // ── Pricing per model (per token, April 2026) ───────────── const MODEL_PRICING = { @@ -1329,11 +1331,15 @@ function getModelPricing(model) { function computeSessionCost(sessionId, project) { const found = findSessionFile(sessionId, project); - if (!found) return { cost: 0, inputTokens: 0, outputTokens: 0, model: '' }; + if (!found) return { cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreateTokens: 0, contextPctSum: 0, contextTurnCount: 0, model: '' }; let totalCost = 0; let totalInput = 0; let totalOutput = 0; + let totalCacheRead = 0; + let totalCacheCreate = 0; + let contextPctSum = 0; + let contextTurnCount = 0; let model = ''; try { @@ -1353,12 +1359,21 @@ function computeSessionCost(sessionId, project) { const cacheRead = u.cache_read_input_tokens || 0; const out = u.output_tokens || 0; - totalInput += inp + cacheCreate + cacheRead; + totalInput += inp; totalOutput += out; + totalCacheRead += cacheRead; + totalCacheCreate += cacheCreate; totalCost += inp * pricing.input + cacheCreate * pricing.cache_create + cacheRead * pricing.cache_read + out * pricing.output; + + // Track per-turn context window usage (average, not peak) + const contextThisTurn = inp + cacheCreate + cacheRead; + if (contextThisTurn > 0) { + contextPctSum += (contextThisTurn / CONTEXT_WINDOW) * 100; + contextTurnCount++; + } } // Codex: estimate from file size (no token usage in session files) } catch {} @@ -1377,7 +1392,7 @@ function computeSessionCost(sessionId, project) { } catch {} } - return { cost: totalCost, inputTokens: totalInput, outputTokens: totalOutput, model }; + return { cost: totalCost, inputTokens: totalInput, outputTokens: totalOutput, cacheReadTokens: totalCacheRead, cacheCreateTokens: totalCacheCreate, contextPctSum, contextTurnCount, model }; } // ── Cost analytics ──────────────────────────────────────── @@ -1386,20 +1401,59 @@ function getCostAnalytics(sessions) { const byDay = {}; const byProject = {}; const byWeek = {}; + const byAgent = {}; let totalCost = 0; let totalTokens = 0; + let totalInputTokens = 0; + let totalOutputTokens = 0; + let totalCacheReadTokens = 0; + let totalCacheCreateTokens = 0; + let globalContextPctSum = 0; + let globalContextTurnCount = 0; + let firstDate = null; + let lastDate = null; + let sessionsWithData = 0; + const agentNoCostData = {}; + for (const s of sessions) { + if (!byAgent[s.tool]) byAgent[s.tool] = { cost: 0, sessions: 0, tokens: 0, estimated: false }; + } const sessionCosts = []; for (const s of sessions) { const costData = computeSessionCost(s.id, s.project); const cost = costData.cost; - const tokens = costData.inputTokens + costData.outputTokens; - if (cost === 0 && tokens === 0) continue; + const tokens = costData.inputTokens + costData.outputTokens + costData.cacheReadTokens + costData.cacheCreateTokens; + if (cost === 0 && tokens === 0) { + if (!agentNoCostData[s.tool]) agentNoCostData[s.tool] = 0; + agentNoCostData[s.tool]++; + continue; + } + sessionsWithData++; totalCost += cost; totalTokens += tokens; - - // By day + totalInputTokens += costData.inputTokens; + totalOutputTokens += costData.outputTokens; + totalCacheReadTokens += costData.cacheReadTokens; + totalCacheCreateTokens += costData.cacheCreateTokens; + + // Per-agent breakdown + const agent = s.tool || 'unknown'; + if (!byAgent[agent]) byAgent[agent] = { cost: 0, sessions: 0, tokens: 0, estimated: false }; + byAgent[agent].cost += cost; + byAgent[agent].sessions++; + byAgent[agent].tokens += tokens; + if (agent === 'codex') byAgent[agent].estimated = true; + + // Context % across all turns + globalContextPctSum += costData.contextPctSum; + globalContextTurnCount += costData.contextTurnCount; + + // Date range const day = s.date || 'unknown'; + if (s.date) { + if (!firstDate || s.date < firstDate) firstDate = s.date; + if (!lastDate || s.date > lastDate) lastDate = s.date; + } if (!byDay[day]) byDay[day] = { cost: 0, sessions: 0, tokens: 0 }; byDay[day].cost += cost; byDay[day].sessions++; @@ -1429,14 +1483,29 @@ function getCostAnalytics(sessions) { // Sort top sessions by cost sessionCosts.sort((a, b) => b.cost - a.cost); + const days = firstDate && lastDate + ? Math.max(1, Math.round((new Date(lastDate) - new Date(firstDate)) / 86400000) + 1) + : 1; + return { totalCost, totalTokens, - totalSessions: sessions.length, + totalInputTokens, + totalOutputTokens, + totalCacheReadTokens, + totalCacheCreateTokens, + avgContextPct: globalContextTurnCount > 0 ? Math.round(globalContextPctSum / globalContextTurnCount) : 0, + dailyRate: totalCost / days, + firstDate, + lastDate, + days, + totalSessions: sessionsWithData, byDay, byWeek, byProject, topSessions: sessionCosts.slice(0, 10), + byAgent, + agentNoCostData, }; }