mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-07 23:54:45 +00:00
* fix(report): surface estimated costs distinctly Providers that estimate tokens or price (kiro, cursor, warp, copilot, grok, hermes, codewhale, and the codex proxy path) set costIsEstimated on their parsed calls, but the flag died at the parser boundary: it was never carried onto ParsedApiCall or into the session aggregates, so a figure whose tokens were synthesized from content length rendered with the same authority as a metered one. Plumb the truth through, mirroring the savingsUSD/isLocalSavings pattern: a call-level boolean (ParsedApiCall.isEstimated, CachedCall.isEstimated, persisted so it survives the session-cache round trip) and an additive aggregate amount (estimatedCostUSD on the model breakdown, session, and project totals, plus PeriodData and the menubar payload). The amount is carried rather than a bare boolean so a row that is mostly metered with a small estimated slice is not indistinguishable from a fully guessed one. Display: report (TUI) and overview per-model rows prefix the cost with a tilde and print one legend line; the MCP tables carry the same marker and legend, and the machine surfaces (report --json, MCP get_usage, menubar / web payload) expose estimatedCostUSD. Totals math is unchanged; the flag is display/metadata only. Bump PROVIDER_PARSE_VERSIONS for every provider that sets the flag so already-cached sessions reparse once and pick it up. Copilot is excluded: it is a durable provider, so changing its env fingerprint would discard OTel cache entries whose source rows may already be pruned. Also fix the cross-provider project merge, which summed totalCostUSD but dropped merged-in projects' totalEstimatedCostUSD, undercounting the project/period estimated total (the same latent gap still affects totalSavingsUSD, left untouched here). * test(parser): pin estimated dollars through the cross-provider merge The merge fix for dropped totalEstimatedCostUSD was not covered: deleting the summing line left every test green. Extract the merge into an exported mergeProjectsByCrossProviderKey (no behavior change) and pin both the measured-plus-estimated and both-estimated merge cases. * docs(parser): honest merge-comment scope and load-bearing overwrite note Re-review nits: the merge doc claimed all additive totals are summed there while totalSavingsUSD still is not (pre-existing gap, tracked separately) and totalProxiedCostUSD is re-derived post-merge; say so. Mark the buildPeriodData overwrite in usage-aggregator as load-bearing for the estimated marker so nobody optimizes it away trusting the daily cache.
237 lines
7 KiB
TypeScript
237 lines
7 KiB
TypeScript
export type TokenUsage = {
|
|
inputTokens: number
|
|
outputTokens: number
|
|
cacheCreationInputTokens: number
|
|
cacheReadInputTokens: number
|
|
cachedInputTokens: number
|
|
reasoningTokens: number
|
|
webSearchRequests: number
|
|
}
|
|
|
|
export type ToolUseBlock = {
|
|
type: 'tool_use'
|
|
id: string
|
|
name: string
|
|
input: Record<string, unknown>
|
|
}
|
|
|
|
export type ContentBlock =
|
|
| { type: 'text'; text: string }
|
|
| { type: 'thinking'; thinking: string }
|
|
| ToolUseBlock
|
|
| { type: string; [key: string]: unknown }
|
|
|
|
export type ApiUsage = {
|
|
input_tokens: number
|
|
output_tokens: number
|
|
cache_creation_input_tokens?: number
|
|
cache_creation?: {
|
|
ephemeral_5m_input_tokens?: number
|
|
ephemeral_1h_input_tokens?: number
|
|
}
|
|
cache_read_input_tokens?: number
|
|
server_tool_use?: {
|
|
web_search_requests?: number
|
|
web_fetch_requests?: number
|
|
}
|
|
speed?: 'standard' | 'fast'
|
|
// Claude Code advisor tool (/advisor): per-turn sub-usage records. A record
|
|
// with type 'advisor_message' carries the advisor model's own tokens and is
|
|
// NOT included in the top-level totals above; type 'message' records mirror
|
|
// the main model and are already covered by the top-level totals.
|
|
iterations?: ApiUsageIteration[]
|
|
}
|
|
|
|
export type ApiUsageIteration = {
|
|
type?: string
|
|
model?: string
|
|
input_tokens?: number
|
|
output_tokens?: number
|
|
cache_creation_input_tokens?: number
|
|
cache_creation?: {
|
|
ephemeral_5m_input_tokens?: number
|
|
ephemeral_1h_input_tokens?: number
|
|
}
|
|
cache_read_input_tokens?: number
|
|
server_tool_use?: {
|
|
web_search_requests?: number
|
|
web_fetch_requests?: number
|
|
}
|
|
speed?: 'standard' | 'fast'
|
|
}
|
|
|
|
export type AssistantMessageContent = {
|
|
model: string
|
|
id?: string
|
|
type: 'message'
|
|
role: 'assistant'
|
|
content: ContentBlock[]
|
|
usage: ApiUsage
|
|
stop_reason?: string
|
|
}
|
|
|
|
export type JournalEntry = {
|
|
type: string
|
|
uuid?: string
|
|
parentUuid?: string | null
|
|
timestamp?: string
|
|
sessionId?: string
|
|
cwd?: string
|
|
version?: string
|
|
gitBranch?: string
|
|
promptId?: string
|
|
message?: AssistantMessageContent | { role: 'user'; content: string | ContentBlock[] }
|
|
isSidechain?: boolean
|
|
[key: string]: unknown
|
|
}
|
|
|
|
export type ParsedTurn = {
|
|
userMessage: string
|
|
assistantCalls: ParsedApiCall[]
|
|
timestamp: string
|
|
sessionId: string
|
|
}
|
|
|
|
export type ParsedApiCall = {
|
|
provider: string
|
|
model: string
|
|
usage: TokenUsage
|
|
costUSD: number
|
|
tools: string[]
|
|
mcpTools: string[]
|
|
skills: string[]
|
|
subagentTypes: string[]
|
|
hasAgentSpawn: boolean
|
|
hasPlanMode: boolean
|
|
speed: 'standard' | 'fast'
|
|
timestamp: string
|
|
bashCommands: string[]
|
|
deduplicationKey: string
|
|
cacheCreationOneHourTokens?: number
|
|
toolSequence?: ToolCall[][]
|
|
/// When set, `costUSD` is the actual local call (forced to 0) and
|
|
/// `savingsUSD` is the counterfactual cost the same tokens would have
|
|
/// incurred against `savingsBaselineModel`. Set by the savings
|
|
/// normalization step in `src/parser.ts`.
|
|
savingsUSD?: number
|
|
savingsBaselineModel?: string
|
|
isLocalSavings?: boolean
|
|
/// True when this call's `costUSD` is priced from estimated token counts or
|
|
/// otherwise synthesized by the provider (e.g. Warp/Kiro/Cursor derive tokens
|
|
/// from content length). Carried from `ParsedProviderCall.costIsEstimated`
|
|
/// across the parser/cache boundary. Aggregates roll the estimated portion up
|
|
/// as `estimatedCostUSD`; it is display/metadata only and never changes totals.
|
|
isEstimated?: boolean
|
|
}
|
|
|
|
export type ToolCall = {
|
|
tool: string
|
|
file?: string
|
|
command?: string
|
|
}
|
|
|
|
export type TaskCategory =
|
|
| 'coding'
|
|
| 'debugging'
|
|
| 'feature'
|
|
| 'refactoring'
|
|
| 'testing'
|
|
| 'exploration'
|
|
| 'planning'
|
|
| 'delegation'
|
|
| 'git'
|
|
| 'build/deploy'
|
|
| 'conversation'
|
|
| 'brainstorming'
|
|
| 'general'
|
|
|
|
export type ClassifiedTurn = ParsedTurn & {
|
|
category: TaskCategory
|
|
subCategory?: string
|
|
retries: number
|
|
hasEdits: boolean
|
|
}
|
|
|
|
export type SessionSourceMetadata = {
|
|
id: string
|
|
label: string
|
|
path: string
|
|
kind: 'claude-config' | 'claude-desktop'
|
|
}
|
|
|
|
export type SessionSummary = {
|
|
sessionId: string
|
|
project: string
|
|
source?: SessionSourceMetadata
|
|
// Claude Code only: agent type of a subagent transcript session
|
|
// (`workflow-subagent`, `Explore`, `general-purpose`, …); undefined for
|
|
// ordinary sessions. Drives the Claude-scoped agent-type breakdown.
|
|
agentType?: string
|
|
firstTimestamp: string
|
|
lastTimestamp: string
|
|
totalCostUSD: number
|
|
totalSavingsUSD: number
|
|
/// Portion of `totalCostUSD` contributed by calls whose price is estimated
|
|
/// (see `ParsedApiCall.isEstimated`). Optional so SessionSummary fixtures and
|
|
/// producers predating the field keep compiling; the parser always sets it.
|
|
totalEstimatedCostUSD?: number
|
|
totalInputTokens: number
|
|
totalOutputTokens: number
|
|
totalReasoningTokens: number
|
|
totalCacheReadTokens: number
|
|
totalCacheWriteTokens: number
|
|
apiCalls: number
|
|
turns: ClassifiedTurn[]
|
|
modelBreakdown: Record<string, { calls: number; costUSD: number; tokens: TokenUsage; savingsUSD: number; estimatedCostUSD?: number }>
|
|
toolBreakdown: Record<string, { calls: number }>
|
|
mcpBreakdown: Record<string, { calls: number }>
|
|
bashBreakdown: Record<string, { calls: number }>
|
|
categoryBreakdown: Record<TaskCategory, { turns: number; costUSD: number; savingsUSD: number; retries: number; editTurns: number; oneShotTurns: number }>
|
|
skillBreakdown: Record<string, { turns: number; costUSD: number; savingsUSD: number; editTurns: number; oneShotTurns: number }>
|
|
subagentBreakdown: Record<string, { calls: number; costUSD: number; savingsUSD: number }>
|
|
// Observed MCP tools available in this session, captured from
|
|
// `attachment.deferred_tools_delta.addedNames` entries. Union across all
|
|
// turns. Each name is a fully-qualified `mcp__<server>__<tool>` identifier.
|
|
// Built-in tools (Bash, Edit, etc.) are filtered out. Provider-agnostic field;
|
|
// currently populated only by the Claude parser.
|
|
mcpInventory?: string[]
|
|
}
|
|
|
|
export type ProjectSummary = {
|
|
project: string
|
|
projectPath: string
|
|
sessions: SessionSummary[]
|
|
totalCostUSD: number
|
|
totalSavingsUSD: number
|
|
/// Portion of `totalCostUSD` priced from estimated tokens (see
|
|
/// `SessionSummary.totalEstimatedCostUSD`). Optional for the same reason.
|
|
totalEstimatedCostUSD?: number
|
|
totalApiCalls: number
|
|
// Portion of `totalCostUSD` served through a subscription-backed proxy
|
|
// (config `proxyPaths`). `totalCostUSD` is left at the full API rate (the
|
|
// billable / would-be figure); this is the subscription-covered amount, so
|
|
// net out-of-pocket for the project is `totalCostUSD - totalProxiedCostUSD`.
|
|
// 0 when the project is not under a configured proxy path.
|
|
totalProxiedCostUSD: number
|
|
}
|
|
|
|
export type DateRange = {
|
|
start: Date
|
|
end: Date
|
|
}
|
|
|
|
export const CATEGORY_LABELS: Record<TaskCategory, string> = {
|
|
coding: 'Coding',
|
|
debugging: 'Debugging',
|
|
feature: 'Feature Dev',
|
|
refactoring: 'Refactoring',
|
|
testing: 'Testing',
|
|
exploration: 'Exploration',
|
|
planning: 'Planning',
|
|
delegation: 'Delegation',
|
|
git: 'Git Ops',
|
|
'build/deploy': 'Build/Deploy',
|
|
conversation: 'Conversation',
|
|
brainstorming: 'Brainstorming',
|
|
general: 'General',
|
|
}
|