Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 59 additions & 6 deletions apps/opencode/src/commands/daily.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@ import { logger } from '../logger.ts';

const TABLE_COLUMN_COUNT = 8;

type TokenStats = {
inputTokens: number;
outputTokens: number;
cacheCreationTokens: number;
cacheReadTokens: number;
cost: number;
};

type ModelBreakdown = TokenStats & {
modelName: string;
};

function createModelBreakdowns(modelAggregates: Map<string, TokenStats>): ModelBreakdown[] {
return Array.from(modelAggregates.entries())
.map(([modelName, stats]) => ({
modelName,
...stats,
}))
.sort((a, b) => b.cost - a.cost);
}

export const dailyCommand = define({
name: 'daily',
description: 'Show OpenCode token usage grouped by day',
Expand Down Expand Up @@ -57,6 +78,7 @@ export const dailyCommand = define({
totalTokens: number;
totalCost: number;
modelsUsed: string[];
modelBreakdowns: ModelBreakdown[];
}> = [];

for (const [date, dayEntries] of Object.entries(entriesByDate)) {
Expand All @@ -66,17 +88,47 @@ export const dailyCommand = define({
let cacheReadTokens = 0;
let totalCost = 0;
const modelsSet = new Set<string>();
const modelAggregates = new Map<string, TokenStats>();
const defaultStats: TokenStats = {
inputTokens: 0,
outputTokens: 0,
cacheCreationTokens: 0,
cacheReadTokens: 0,
cost: 0,
};

for (const entry of dayEntries) {
inputTokens += entry.usage.inputTokens;
outputTokens += entry.usage.outputTokens;
cacheCreationTokens += entry.usage.cacheCreationInputTokens;
cacheReadTokens += entry.usage.cacheReadInputTokens;
totalCost += await calculateCostForEntry(entry, fetcher);
modelsSet.add(entry.model);
const modelName = entry.model ?? 'unknown';
if (modelName === '<synthetic>') {
continue;
}

const entryInputTokens = entry.usage.inputTokens;
const entryOutputTokens = entry.usage.outputTokens;
const entryCacheCreation = entry.usage.cacheCreationInputTokens;
const entryCacheRead = entry.usage.cacheReadInputTokens;
const entryCost = await calculateCostForEntry(entry, fetcher);

inputTokens += entryInputTokens;
outputTokens += entryOutputTokens;
cacheCreationTokens += entryCacheCreation;
cacheReadTokens += entryCacheRead;
totalCost += entryCost;
modelsSet.add(modelName);

const existing = modelAggregates.get(modelName) ?? defaultStats;

modelAggregates.set(modelName, {
inputTokens: existing.inputTokens + entryInputTokens,
outputTokens: existing.outputTokens + entryOutputTokens,
cacheCreationTokens: existing.cacheCreationTokens + entryCacheCreation,
cacheReadTokens: existing.cacheReadTokens + entryCacheRead,
cost: existing.cost + entryCost,
});
}

const totalTokens = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
const modelBreakdowns = createModelBreakdowns(modelAggregates);

dailyData.push({
date,
Expand All @@ -87,6 +139,7 @@ export const dailyCommand = define({
totalTokens,
totalCost,
modelsUsed: Array.from(modelsSet),
modelBreakdowns,
});
}

Expand Down
65 changes: 59 additions & 6 deletions apps/opencode/src/commands/monthly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@ import { logger } from '../logger.ts';

const TABLE_COLUMN_COUNT = 8;

type TokenStats = {
inputTokens: number;
outputTokens: number;
cacheCreationTokens: number;
cacheReadTokens: number;
cost: number;
};

type ModelBreakdown = TokenStats & {
modelName: string;
};

function createModelBreakdowns(modelAggregates: Map<string, TokenStats>): ModelBreakdown[] {
return Array.from(modelAggregates.entries())
.map(([modelName, stats]) => ({
modelName,
...stats,
}))
.sort((a, b) => b.cost - a.cost);
}

export const monthlyCommand = define({
name: 'monthly',
description: 'Show OpenCode token usage grouped by month',
Expand Down Expand Up @@ -57,6 +78,7 @@ export const monthlyCommand = define({
totalTokens: number;
totalCost: number;
modelsUsed: string[];
modelBreakdowns: ModelBreakdown[];
}> = [];

for (const [month, monthEntries] of Object.entries(entriesByMonth)) {
Expand All @@ -66,17 +88,47 @@ export const monthlyCommand = define({
let cacheReadTokens = 0;
let totalCost = 0;
const modelsSet = new Set<string>();
const modelAggregates = new Map<string, TokenStats>();
const defaultStats: TokenStats = {
inputTokens: 0,
outputTokens: 0,
cacheCreationTokens: 0,
cacheReadTokens: 0,
cost: 0,
};

for (const entry of monthEntries) {
inputTokens += entry.usage.inputTokens;
outputTokens += entry.usage.outputTokens;
cacheCreationTokens += entry.usage.cacheCreationInputTokens;
cacheReadTokens += entry.usage.cacheReadInputTokens;
totalCost += await calculateCostForEntry(entry, fetcher);
modelsSet.add(entry.model);
const modelName = entry.model ?? 'unknown';
if (modelName === '<synthetic>') {
continue;
}

const entryInputTokens = entry.usage.inputTokens;
const entryOutputTokens = entry.usage.outputTokens;
const entryCacheCreation = entry.usage.cacheCreationInputTokens;
const entryCacheRead = entry.usage.cacheReadInputTokens;
const entryCost = await calculateCostForEntry(entry, fetcher);

inputTokens += entryInputTokens;
outputTokens += entryOutputTokens;
cacheCreationTokens += entryCacheCreation;
cacheReadTokens += entryCacheRead;
totalCost += entryCost;
modelsSet.add(modelName);

const existing = modelAggregates.get(modelName) ?? defaultStats;

modelAggregates.set(modelName, {
inputTokens: existing.inputTokens + entryInputTokens,
outputTokens: existing.outputTokens + entryOutputTokens,
cacheCreationTokens: existing.cacheCreationTokens + entryCacheCreation,
cacheReadTokens: existing.cacheReadTokens + entryCacheRead,
cost: existing.cost + entryCost,
});
}

const totalTokens = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
const modelBreakdowns = createModelBreakdowns(modelAggregates);

monthlyData.push({
month,
Expand All @@ -87,6 +139,7 @@ export const monthlyCommand = define({
totalTokens,
totalCost,
modelsUsed: Array.from(modelsSet),
modelBreakdowns,
});
}

Expand Down
65 changes: 59 additions & 6 deletions apps/opencode/src/commands/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@ import { logger } from '../logger.ts';

const TABLE_COLUMN_COUNT = 8;

type TokenStats = {
inputTokens: number;
outputTokens: number;
cacheCreationTokens: number;
cacheReadTokens: number;
cost: number;
};

type ModelBreakdown = TokenStats & {
modelName: string;
};

function createModelBreakdowns(modelAggregates: Map<string, TokenStats>): ModelBreakdown[] {
return Array.from(modelAggregates.entries())
.map(([modelName, stats]) => ({
modelName,
...stats,
}))
.sort((a, b) => b.cost - a.cost);
}

export const sessionCommand = define({
name: 'session',
description: 'Show OpenCode token usage grouped by session',
Expand Down Expand Up @@ -62,6 +83,7 @@ export const sessionCommand = define({
totalTokens: number;
totalCost: number;
modelsUsed: string[];
modelBreakdowns: ModelBreakdown[];
lastActivity: Date;
};

Expand All @@ -74,22 +96,52 @@ export const sessionCommand = define({
let cacheReadTokens = 0;
let totalCost = 0;
const modelsSet = new Set<string>();
const modelAggregates = new Map<string, TokenStats>();
const defaultStats: TokenStats = {
inputTokens: 0,
outputTokens: 0,
cacheCreationTokens: 0,
cacheReadTokens: 0,
cost: 0,
};
let lastActivity = sessionEntries[0]!.timestamp;

for (const entry of sessionEntries) {
inputTokens += entry.usage.inputTokens;
outputTokens += entry.usage.outputTokens;
cacheCreationTokens += entry.usage.cacheCreationInputTokens;
cacheReadTokens += entry.usage.cacheReadInputTokens;
totalCost += await calculateCostForEntry(entry, fetcher);
modelsSet.add(entry.model);
const modelName = entry.model ?? 'unknown';
if (modelName === '<synthetic>') {
continue;
}

const entryInputTokens = entry.usage.inputTokens;
const entryOutputTokens = entry.usage.outputTokens;
const entryCacheCreation = entry.usage.cacheCreationInputTokens;
const entryCacheRead = entry.usage.cacheReadInputTokens;
const entryCost = await calculateCostForEntry(entry, fetcher);

inputTokens += entryInputTokens;
outputTokens += entryOutputTokens;
cacheCreationTokens += entryCacheCreation;
cacheReadTokens += entryCacheRead;
totalCost += entryCost;
modelsSet.add(modelName);

const existing = modelAggregates.get(modelName) ?? defaultStats;

modelAggregates.set(modelName, {
inputTokens: existing.inputTokens + entryInputTokens,
outputTokens: existing.outputTokens + entryOutputTokens,
cacheCreationTokens: existing.cacheCreationTokens + entryCacheCreation,
cacheReadTokens: existing.cacheReadTokens + entryCacheRead,
cost: existing.cost + entryCost,
});

if (entry.timestamp > lastActivity) {
lastActivity = entry.timestamp;
}
}

const totalTokens = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
const modelBreakdowns = createModelBreakdowns(modelAggregates);

const metadata = sessionMetadataMap.get(sessionID);

Expand All @@ -104,6 +156,7 @@ export const sessionCommand = define({
totalTokens,
totalCost,
modelsUsed: Array.from(modelsSet),
modelBreakdowns,
lastActivity,
});
}
Expand Down
65 changes: 59 additions & 6 deletions apps/opencode/src/commands/weekly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@ import { logger } from '../logger.ts';

const TABLE_COLUMN_COUNT = 8;

type TokenStats = {
inputTokens: number;
outputTokens: number;
cacheCreationTokens: number;
cacheReadTokens: number;
cost: number;
};

type ModelBreakdown = TokenStats & {
modelName: string;
};

function createModelBreakdowns(modelAggregates: Map<string, TokenStats>): ModelBreakdown[] {
return Array.from(modelAggregates.entries())
.map(([modelName, stats]) => ({
modelName,
...stats,
}))
.sort((a, b) => b.cost - a.cost);
}

/**
* Get ISO week number for a date
* ISO week starts on Monday, first week contains Jan 4th
Expand Down Expand Up @@ -82,6 +103,7 @@ export const weeklyCommand = define({
totalTokens: number;
totalCost: number;
modelsUsed: string[];
modelBreakdowns: ModelBreakdown[];
}> = [];

for (const [week, weekEntries] of Object.entries(entriesByWeek)) {
Expand All @@ -91,17 +113,47 @@ export const weeklyCommand = define({
let cacheReadTokens = 0;
let totalCost = 0;
const modelsSet = new Set<string>();
const modelAggregates = new Map<string, TokenStats>();
const defaultStats: TokenStats = {
inputTokens: 0,
outputTokens: 0,
cacheCreationTokens: 0,
cacheReadTokens: 0,
cost: 0,
};

for (const entry of weekEntries) {
inputTokens += entry.usage.inputTokens;
outputTokens += entry.usage.outputTokens;
cacheCreationTokens += entry.usage.cacheCreationInputTokens;
cacheReadTokens += entry.usage.cacheReadInputTokens;
totalCost += await calculateCostForEntry(entry, fetcher);
modelsSet.add(entry.model);
const modelName = entry.model ?? 'unknown';
if (modelName === '<synthetic>') {
continue;
}

const entryInputTokens = entry.usage.inputTokens;
const entryOutputTokens = entry.usage.outputTokens;
const entryCacheCreation = entry.usage.cacheCreationInputTokens;
const entryCacheRead = entry.usage.cacheReadInputTokens;
const entryCost = await calculateCostForEntry(entry, fetcher);

inputTokens += entryInputTokens;
outputTokens += entryOutputTokens;
cacheCreationTokens += entryCacheCreation;
cacheReadTokens += entryCacheRead;
totalCost += entryCost;
modelsSet.add(modelName);

const existing = modelAggregates.get(modelName) ?? defaultStats;

modelAggregates.set(modelName, {
inputTokens: existing.inputTokens + entryInputTokens,
outputTokens: existing.outputTokens + entryOutputTokens,
cacheCreationTokens: existing.cacheCreationTokens + entryCacheCreation,
cacheReadTokens: existing.cacheReadTokens + entryCacheRead,
cost: existing.cost + entryCost,
});
}

const totalTokens = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
const modelBreakdowns = createModelBreakdowns(modelAggregates);

weeklyData.push({
week,
Expand All @@ -112,6 +164,7 @@ export const weeklyCommand = define({
totalTokens,
totalCost,
modelsUsed: Array.from(modelsSet),
modelBreakdowns,
});
}

Expand Down