agentfit 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +6 -2
- package/LICENSE +662 -21
- package/README.md +1 -1
- package/components/daily-table.tsx +22 -13
- package/components/data-provider.tsx +4 -0
- package/electron/main.mjs +39 -0
- package/generated/prisma/browser.ts +5 -0
- package/generated/prisma/client.ts +5 -0
- package/generated/prisma/internal/class.ts +14 -4
- package/generated/prisma/internal/prismaNamespace.ts +97 -1
- package/generated/prisma/internal/prismaNamespaceBrowser.ts +21 -0
- package/generated/prisma/models/MessageUsage.ts +1473 -0
- package/generated/prisma/models.ts +1 -0
- package/lib/parse-logs.ts +11 -0
- package/lib/pricing.ts +103 -37
- package/lib/queries-codex.ts +1 -0
- package/lib/queries.ts +124 -36
- package/lib/sync.ts +212 -67
- package/package.json +3 -2
- package/prisma/migrations/20260505162205_add_message_usage/migration.sql +29 -0
- package/prisma/schema.prisma +22 -0
- package/prisma/schema.sql +30 -0
package/lib/parse-logs.ts
CHANGED
|
@@ -49,6 +49,15 @@ export interface ProjectSummary {
|
|
|
49
49
|
toolCalls: Record<string, number>
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export interface ModelBreakdown {
|
|
53
|
+
model: string
|
|
54
|
+
inputTokens: number
|
|
55
|
+
outputTokens: number
|
|
56
|
+
cacheCreationTokens: number
|
|
57
|
+
cacheReadTokens: number
|
|
58
|
+
costUSD: number
|
|
59
|
+
}
|
|
60
|
+
|
|
52
61
|
export interface DailyUsage {
|
|
53
62
|
date: string
|
|
54
63
|
sessions: number
|
|
@@ -65,6 +74,7 @@ export interface DailyUsage {
|
|
|
65
74
|
toolCallsDetail: Record<string, number>
|
|
66
75
|
interruptions: number
|
|
67
76
|
rateLimitErrors: number
|
|
77
|
+
modelBreakdowns: ModelBreakdown[]
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
export interface OverviewStats {
|
|
@@ -359,6 +369,7 @@ export async function parseAllLogs(): Promise<UsageData> {
|
|
|
359
369
|
toolCallsDetail: {},
|
|
360
370
|
interruptions: 0,
|
|
361
371
|
rateLimitErrors: 0,
|
|
372
|
+
modelBreakdowns: [],
|
|
362
373
|
})
|
|
363
374
|
}
|
|
364
375
|
const daily = dailyMap.get(date)!
|
package/lib/pricing.ts
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
|
-
// Fetches pricing from LiteLLM's model pricing database (same source as ccusage)
|
|
1
|
+
// Fetches pricing from LiteLLM's model pricing database (same source as ccusage).
|
|
2
|
+
// Tiered (>200k) pricing and the speed=fast multiplier are ported from ccusage
|
|
3
|
+
// (MIT, © 2025 ryoppippi) — see packages/internal/src/pricing.ts in that repo.
|
|
2
4
|
|
|
3
5
|
const LITELLM_PRICING_URL =
|
|
4
6
|
'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json'
|
|
5
7
|
|
|
8
|
+
const TIERED_THRESHOLD = 200_000
|
|
9
|
+
|
|
6
10
|
export interface ModelPricing {
|
|
7
11
|
input_cost_per_token?: number
|
|
8
12
|
output_cost_per_token?: number
|
|
9
13
|
cache_creation_input_token_cost?: number
|
|
10
14
|
cache_read_input_token_cost?: number
|
|
15
|
+
// 1M-context tiered prices (Claude/Anthropic only)
|
|
16
|
+
input_cost_per_token_above_200k_tokens?: number
|
|
17
|
+
output_cost_per_token_above_200k_tokens?: number
|
|
18
|
+
cache_creation_input_token_cost_above_200k_tokens?: number
|
|
19
|
+
cache_read_input_token_cost_above_200k_tokens?: number
|
|
20
|
+
provider_specific_entry?: { fast?: number }
|
|
11
21
|
}
|
|
12
22
|
|
|
23
|
+
export type Speed = 'standard' | 'fast'
|
|
24
|
+
|
|
13
25
|
let pricingCache: Record<string, ModelPricing> | null = null
|
|
14
26
|
|
|
15
27
|
export async function loadPricing(): Promise<Record<string, ModelPricing>> {
|
|
@@ -18,32 +30,46 @@ export async function loadPricing(): Promise<Record<string, ModelPricing>> {
|
|
|
18
30
|
try {
|
|
19
31
|
const res = await fetch(LITELLM_PRICING_URL, { next: { revalidate: 86400 } })
|
|
20
32
|
const data = await res.json()
|
|
21
|
-
// Filter to Claude/Anthropic models only
|
|
22
33
|
const filtered: Record<string, ModelPricing> = {}
|
|
23
34
|
for (const [key, value] of Object.entries(data)) {
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
if (!key.includes('claude') && !key.includes('anthropic')) continue
|
|
36
|
+
const v = value as Record<string, unknown>
|
|
37
|
+
filtered[key] = {
|
|
38
|
+
input_cost_per_token: v.input_cost_per_token as number | undefined,
|
|
39
|
+
output_cost_per_token: v.output_cost_per_token as number | undefined,
|
|
40
|
+
cache_creation_input_token_cost: v.cache_creation_input_token_cost as number | undefined,
|
|
41
|
+
cache_read_input_token_cost: v.cache_read_input_token_cost as number | undefined,
|
|
42
|
+
input_cost_per_token_above_200k_tokens: v.input_cost_per_token_above_200k_tokens as
|
|
43
|
+
| number
|
|
44
|
+
| undefined,
|
|
45
|
+
output_cost_per_token_above_200k_tokens: v.output_cost_per_token_above_200k_tokens as
|
|
46
|
+
| number
|
|
47
|
+
| undefined,
|
|
48
|
+
cache_creation_input_token_cost_above_200k_tokens:
|
|
49
|
+
v.cache_creation_input_token_cost_above_200k_tokens as number | undefined,
|
|
50
|
+
cache_read_input_token_cost_above_200k_tokens:
|
|
51
|
+
v.cache_read_input_token_cost_above_200k_tokens as number | undefined,
|
|
52
|
+
provider_specific_entry:
|
|
53
|
+
v.provider_specific_entry && typeof v.provider_specific_entry === 'object'
|
|
54
|
+
? { fast: (v.provider_specific_entry as Record<string, unknown>).fast as number | undefined }
|
|
55
|
+
: undefined,
|
|
35
56
|
}
|
|
36
57
|
}
|
|
37
58
|
pricingCache = filtered
|
|
38
59
|
return filtered
|
|
39
60
|
} catch {
|
|
40
|
-
// Fallback pricing if fetch fails
|
|
41
61
|
return getFallbackPricing()
|
|
42
62
|
}
|
|
43
63
|
}
|
|
44
64
|
|
|
45
65
|
function getFallbackPricing(): Record<string, ModelPricing> {
|
|
46
66
|
return {
|
|
67
|
+
'claude-opus-4-7': {
|
|
68
|
+
input_cost_per_token: 15e-6,
|
|
69
|
+
output_cost_per_token: 75e-6,
|
|
70
|
+
cache_creation_input_token_cost: 18.75e-6,
|
|
71
|
+
cache_read_input_token_cost: 1.5e-6,
|
|
72
|
+
},
|
|
47
73
|
'claude-opus-4-6': {
|
|
48
74
|
input_cost_per_token: 15e-6,
|
|
49
75
|
output_cost_per_token: 75e-6,
|
|
@@ -55,12 +81,16 @@ function getFallbackPricing(): Record<string, ModelPricing> {
|
|
|
55
81
|
output_cost_per_token: 15e-6,
|
|
56
82
|
cache_creation_input_token_cost: 3.75e-6,
|
|
57
83
|
cache_read_input_token_cost: 0.3e-6,
|
|
84
|
+
input_cost_per_token_above_200k_tokens: 6e-6,
|
|
85
|
+
output_cost_per_token_above_200k_tokens: 22.5e-6,
|
|
86
|
+
cache_creation_input_token_cost_above_200k_tokens: 7.5e-6,
|
|
87
|
+
cache_read_input_token_cost_above_200k_tokens: 0.6e-6,
|
|
58
88
|
},
|
|
59
89
|
'claude-haiku-4-5-20251001': {
|
|
60
|
-
input_cost_per_token:
|
|
61
|
-
output_cost_per_token:
|
|
62
|
-
cache_creation_input_token_cost:
|
|
63
|
-
cache_read_input_token_cost: 0.
|
|
90
|
+
input_cost_per_token: 1e-6,
|
|
91
|
+
output_cost_per_token: 5e-6,
|
|
92
|
+
cache_creation_input_token_cost: 1.25e-6,
|
|
93
|
+
cache_read_input_token_cost: 0.1e-6,
|
|
64
94
|
},
|
|
65
95
|
}
|
|
66
96
|
}
|
|
@@ -68,28 +98,43 @@ function getFallbackPricing(): Record<string, ModelPricing> {
|
|
|
68
98
|
export function findPricing(
|
|
69
99
|
model: string,
|
|
70
100
|
allPricing: Record<string, ModelPricing>
|
|
71
|
-
): ModelPricing {
|
|
72
|
-
// Try exact match
|
|
101
|
+
): ModelPricing | null {
|
|
73
102
|
if (allPricing[model]) return allPricing[model]
|
|
74
|
-
|
|
75
|
-
// Try with anthropic/ prefix
|
|
76
103
|
if (allPricing[`anthropic/${model}`]) return allPricing[`anthropic/${model}`]
|
|
77
104
|
|
|
78
|
-
// Try partial match
|
|
79
105
|
for (const [key, pricing] of Object.entries(allPricing)) {
|
|
80
106
|
const normalizedKey = key.replace('anthropic/', '').replace('anthropic.', '')
|
|
81
|
-
if (
|
|
107
|
+
if (
|
|
108
|
+
normalizedKey === model ||
|
|
109
|
+
normalizedKey.startsWith(model) ||
|
|
110
|
+
model.startsWith(normalizedKey)
|
|
111
|
+
) {
|
|
82
112
|
return pricing
|
|
83
113
|
}
|
|
84
114
|
}
|
|
85
115
|
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
cache_read_input_token_cost: 0.3e-6,
|
|
116
|
+
// Substring fallback (mirrors ccusage's behaviour at packages/internal/src/pricing.ts:232-238)
|
|
117
|
+
const lower = model.toLowerCase()
|
|
118
|
+
for (const [key, pricing] of Object.entries(allPricing)) {
|
|
119
|
+
const k = key.toLowerCase()
|
|
120
|
+
if (k.includes(lower) || lower.includes(k)) return pricing
|
|
92
121
|
}
|
|
122
|
+
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function tieredCost(
|
|
127
|
+
total: number | undefined,
|
|
128
|
+
base: number | undefined,
|
|
129
|
+
tiered: number | undefined
|
|
130
|
+
): number {
|
|
131
|
+
if (total == null || total <= 0) return 0
|
|
132
|
+
if (total > TIERED_THRESHOLD && tiered != null) {
|
|
133
|
+
const below = Math.min(total, TIERED_THRESHOLD)
|
|
134
|
+
const above = Math.max(0, total - TIERED_THRESHOLD)
|
|
135
|
+
return above * tiered + (base != null ? below * base : 0)
|
|
136
|
+
}
|
|
137
|
+
return base != null ? total * base : 0
|
|
93
138
|
}
|
|
94
139
|
|
|
95
140
|
export function calculateCost(
|
|
@@ -100,13 +145,34 @@ export function calculateCost(
|
|
|
100
145
|
cache_creation_input_tokens?: number
|
|
101
146
|
cache_read_input_tokens?: number
|
|
102
147
|
},
|
|
103
|
-
allPricing: Record<string, ModelPricing
|
|
148
|
+
allPricing: Record<string, ModelPricing>,
|
|
149
|
+
speed: Speed = 'standard'
|
|
104
150
|
): number {
|
|
105
151
|
const pricing = findPricing(model, allPricing)
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
(
|
|
110
|
-
|
|
111
|
-
|
|
152
|
+
if (!pricing) return 0
|
|
153
|
+
|
|
154
|
+
const base =
|
|
155
|
+
tieredCost(
|
|
156
|
+
usage.input_tokens,
|
|
157
|
+
pricing.input_cost_per_token,
|
|
158
|
+
pricing.input_cost_per_token_above_200k_tokens
|
|
159
|
+
) +
|
|
160
|
+
tieredCost(
|
|
161
|
+
usage.output_tokens,
|
|
162
|
+
pricing.output_cost_per_token,
|
|
163
|
+
pricing.output_cost_per_token_above_200k_tokens
|
|
164
|
+
) +
|
|
165
|
+
tieredCost(
|
|
166
|
+
usage.cache_creation_input_tokens,
|
|
167
|
+
pricing.cache_creation_input_token_cost,
|
|
168
|
+
pricing.cache_creation_input_token_cost_above_200k_tokens
|
|
169
|
+
) +
|
|
170
|
+
tieredCost(
|
|
171
|
+
usage.cache_read_input_tokens,
|
|
172
|
+
pricing.cache_read_input_token_cost,
|
|
173
|
+
pricing.cache_read_input_token_cost_above_200k_tokens
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
const multiplier = speed === 'fast' ? pricing.provider_specific_entry?.fast ?? 1 : 1
|
|
177
|
+
return base * multiplier
|
|
112
178
|
}
|
package/lib/queries-codex.ts
CHANGED
package/lib/queries.ts
CHANGED
|
@@ -4,15 +4,28 @@ import type {
|
|
|
4
4
|
SessionSummary,
|
|
5
5
|
ProjectSummary,
|
|
6
6
|
DailyUsage,
|
|
7
|
+
ModelBreakdown,
|
|
7
8
|
OverviewStats,
|
|
8
9
|
} from './parse-logs'
|
|
9
10
|
|
|
10
11
|
export async function getUsageData(): Promise<UsageData> {
|
|
11
|
-
const dbSessions = await
|
|
12
|
-
orderBy: { startTime: 'desc' },
|
|
13
|
-
|
|
12
|
+
const [dbSessions, dbMessageUsages] = await Promise.all([
|
|
13
|
+
prisma.session.findMany({ orderBy: { startTime: 'desc' } }),
|
|
14
|
+
prisma.messageUsage.findMany({
|
|
15
|
+
select: {
|
|
16
|
+
date: true,
|
|
17
|
+
model: true,
|
|
18
|
+
speed: true,
|
|
19
|
+
inputTokens: true,
|
|
20
|
+
outputTokens: true,
|
|
21
|
+
cacheCreationTokens: true,
|
|
22
|
+
cacheReadTokens: true,
|
|
23
|
+
costUSD: true,
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
])
|
|
14
27
|
|
|
15
|
-
if (dbSessions.length === 0) {
|
|
28
|
+
if (dbSessions.length === 0 && dbMessageUsages.length === 0) {
|
|
16
29
|
return emptyUsageData()
|
|
17
30
|
}
|
|
18
31
|
|
|
@@ -47,13 +60,12 @@ export async function getUsageData(): Promise<UsageData> {
|
|
|
47
60
|
modelCounts: JSON.parse(s.modelCountsJson || '{}') as Record<string, number>,
|
|
48
61
|
}))
|
|
49
62
|
|
|
50
|
-
// Aggregate projects
|
|
63
|
+
// Aggregate projects (per-session — unaffected by message-level dedup since
|
|
64
|
+
// a session belongs to one project)
|
|
51
65
|
const projectMap = new Map<string, ProjectSummary>()
|
|
52
|
-
const dailyMap = new Map<string, DailyUsage>()
|
|
53
66
|
const toolUsage: Record<string, number> = {}
|
|
54
67
|
|
|
55
68
|
for (const s of sessions) {
|
|
56
|
-
// Projects
|
|
57
69
|
if (!projectMap.has(s.project)) {
|
|
58
70
|
projectMap.set(s.project, {
|
|
59
71
|
name: s.project,
|
|
@@ -75,12 +87,23 @@ export async function getUsageData(): Promise<UsageData> {
|
|
|
75
87
|
for (const [tool, count] of Object.entries(s.toolCalls)) {
|
|
76
88
|
proj.toolCalls[tool] = (proj.toolCalls[tool] || 0) + count
|
|
77
89
|
}
|
|
90
|
+
for (const [tool, count] of Object.entries(s.toolCalls)) {
|
|
91
|
+
toolUsage[tool] = (toolUsage[tool] || 0) + count
|
|
92
|
+
}
|
|
93
|
+
}
|
|
78
94
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
// Daily aggregation comes from MessageUsage so totals match ccusage:
|
|
96
|
+
// • per-message timestamp → correct date bucket across midnight
|
|
97
|
+
// • (messageId, requestId) dedup → no double-counting on session resumes
|
|
98
|
+
// • per-(date, model) breakdown → opus + haiku stack on the same day
|
|
99
|
+
// Mirrors ccusage data-loader.ts:760-901 (loadDailyUsageData).
|
|
100
|
+
const dailyMap = new Map<string, DailyUsage>()
|
|
101
|
+
const breakdownMap = new Map<string, Map<string, ModelBreakdown>>()
|
|
102
|
+
|
|
103
|
+
for (const m of dbMessageUsages) {
|
|
104
|
+
if (!dailyMap.has(m.date)) {
|
|
105
|
+
dailyMap.set(m.date, {
|
|
106
|
+
date: m.date,
|
|
84
107
|
sessions: 0,
|
|
85
108
|
messages: 0,
|
|
86
109
|
userMessages: 0,
|
|
@@ -95,32 +118,84 @@ export async function getUsageData(): Promise<UsageData> {
|
|
|
95
118
|
toolCallsDetail: {},
|
|
96
119
|
interruptions: 0,
|
|
97
120
|
rateLimitErrors: 0,
|
|
121
|
+
modelBreakdowns: [],
|
|
98
122
|
})
|
|
123
|
+
breakdownMap.set(m.date, new Map())
|
|
99
124
|
}
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
125
|
+
const d = dailyMap.get(m.date)!
|
|
126
|
+
d.inputTokens += m.inputTokens
|
|
127
|
+
d.outputTokens += m.outputTokens
|
|
128
|
+
d.cacheCreationTokens += m.cacheCreationTokens
|
|
129
|
+
d.cacheReadTokens += m.cacheReadTokens
|
|
130
|
+
d.totalTokens +=
|
|
131
|
+
m.inputTokens + m.outputTokens + m.cacheCreationTokens + m.cacheReadTokens
|
|
132
|
+
d.costUSD += m.costUSD
|
|
133
|
+
|
|
134
|
+
const modelKey = m.speed === 'fast' ? `${m.model}-fast` : m.model
|
|
135
|
+
const perModel = breakdownMap.get(m.date)!
|
|
136
|
+
if (!perModel.has(modelKey)) {
|
|
137
|
+
perModel.set(modelKey, {
|
|
138
|
+
model: modelKey,
|
|
139
|
+
inputTokens: 0,
|
|
140
|
+
outputTokens: 0,
|
|
141
|
+
cacheCreationTokens: 0,
|
|
142
|
+
cacheReadTokens: 0,
|
|
143
|
+
costUSD: 0,
|
|
144
|
+
})
|
|
116
145
|
}
|
|
146
|
+
const mb = perModel.get(modelKey)!
|
|
147
|
+
mb.inputTokens += m.inputTokens
|
|
148
|
+
mb.outputTokens += m.outputTokens
|
|
149
|
+
mb.cacheCreationTokens += m.cacheCreationTokens
|
|
150
|
+
mb.cacheReadTokens += m.cacheReadTokens
|
|
151
|
+
mb.costUSD += m.costUSD
|
|
152
|
+
}
|
|
117
153
|
|
|
118
|
-
|
|
154
|
+
// Layer per-session counters that MessageUsage doesn't track: sessions count,
|
|
155
|
+
// user/assistant message counts, tool calls, interruptions.
|
|
156
|
+
for (const s of sessions) {
|
|
157
|
+
const date = s.startTime.slice(0, 10)
|
|
158
|
+
let d = dailyMap.get(date)
|
|
159
|
+
if (!d) {
|
|
160
|
+
d = {
|
|
161
|
+
date,
|
|
162
|
+
sessions: 0,
|
|
163
|
+
messages: 0,
|
|
164
|
+
userMessages: 0,
|
|
165
|
+
assistantMessages: 0,
|
|
166
|
+
inputTokens: 0,
|
|
167
|
+
outputTokens: 0,
|
|
168
|
+
cacheCreationTokens: 0,
|
|
169
|
+
cacheReadTokens: 0,
|
|
170
|
+
totalTokens: 0,
|
|
171
|
+
costUSD: 0,
|
|
172
|
+
toolCalls: 0,
|
|
173
|
+
toolCallsDetail: {},
|
|
174
|
+
interruptions: 0,
|
|
175
|
+
rateLimitErrors: 0,
|
|
176
|
+
modelBreakdowns: [],
|
|
177
|
+
}
|
|
178
|
+
dailyMap.set(date, d)
|
|
179
|
+
breakdownMap.set(date, new Map())
|
|
180
|
+
}
|
|
181
|
+
d.sessions++
|
|
182
|
+
d.messages += s.totalMessages
|
|
183
|
+
d.userMessages += s.userMessages
|
|
184
|
+
d.assistantMessages += s.assistantMessages
|
|
185
|
+
d.toolCalls += s.toolCallsTotal
|
|
186
|
+
d.interruptions += s.userInterruptions
|
|
187
|
+
d.rateLimitErrors += s.rateLimitErrors
|
|
119
188
|
for (const [tool, count] of Object.entries(s.toolCalls)) {
|
|
120
|
-
|
|
189
|
+
d.toolCallsDetail[tool] = (d.toolCallsDetail[tool] || 0) + count
|
|
121
190
|
}
|
|
122
191
|
}
|
|
123
192
|
|
|
193
|
+
for (const [date, perModel] of breakdownMap) {
|
|
194
|
+
dailyMap.get(date)!.modelBreakdowns = Array.from(perModel.values()).sort((a, b) =>
|
|
195
|
+
a.model.localeCompare(b.model)
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
124
199
|
const dailyArr = Array.from(dailyMap.values()).sort((a, b) => a.date.localeCompare(b.date))
|
|
125
200
|
const projects = Array.from(projectMap.values()).sort((a, b) => b.totalCost - a.totalCost)
|
|
126
201
|
|
|
@@ -139,18 +214,31 @@ export async function getUsageData(): Promise<UsageData> {
|
|
|
139
214
|
}
|
|
140
215
|
}
|
|
141
216
|
|
|
217
|
+
// Overview token + cost totals come from the deduped MessageUsage rows so
|
|
218
|
+
// the overview matches the daily breakdown (which also reads MessageUsage).
|
|
219
|
+
const tokenTotals = dailyArr.reduce(
|
|
220
|
+
(acc, d) => ({
|
|
221
|
+
input: acc.input + d.inputTokens,
|
|
222
|
+
output: acc.output + d.outputTokens,
|
|
223
|
+
cc: acc.cc + d.cacheCreationTokens,
|
|
224
|
+
cr: acc.cr + d.cacheReadTokens,
|
|
225
|
+
cost: acc.cost + d.costUSD,
|
|
226
|
+
}),
|
|
227
|
+
{ input: 0, output: 0, cc: 0, cr: 0, cost: 0 }
|
|
228
|
+
)
|
|
229
|
+
|
|
142
230
|
const overview: OverviewStats = {
|
|
143
231
|
totalSessions: sessions.length,
|
|
144
232
|
totalProjects: projects.length,
|
|
145
233
|
totalMessages: sessions.reduce((a, s) => a + s.totalMessages, 0),
|
|
146
234
|
totalUserMessages: sessions.reduce((a, s) => a + s.userMessages, 0),
|
|
147
235
|
totalAssistantMessages: sessions.reduce((a, s) => a + s.assistantMessages, 0),
|
|
148
|
-
totalInputTokens:
|
|
149
|
-
totalOutputTokens:
|
|
150
|
-
totalCacheCreationTokens:
|
|
151
|
-
totalCacheReadTokens:
|
|
152
|
-
totalTokens:
|
|
153
|
-
totalCostUSD:
|
|
236
|
+
totalInputTokens: tokenTotals.input,
|
|
237
|
+
totalOutputTokens: tokenTotals.output,
|
|
238
|
+
totalCacheCreationTokens: tokenTotals.cc,
|
|
239
|
+
totalCacheReadTokens: tokenTotals.cr,
|
|
240
|
+
totalTokens: tokenTotals.input + tokenTotals.output + tokenTotals.cc + tokenTotals.cr,
|
|
241
|
+
totalCostUSD: tokenTotals.cost,
|
|
154
242
|
totalDurationMinutes: sessions.reduce((a, s) => a + s.durationMinutes, 0),
|
|
155
243
|
totalToolCalls: sessions.reduce((a, s) => a + s.toolCallsTotal, 0),
|
|
156
244
|
totalApiErrors: sessions.reduce((a, s) => a + s.apiErrors, 0),
|