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
5 changes: 5 additions & 0 deletions .changeset/usage-cache-stats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": minor
---

Show prompt cache read and write token counts in the usage report.
27 changes: 27 additions & 0 deletions apps/kimi-code/src/tui/components/messages/usage-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ function usageInputTotal(usage: TokenUsage): number {
);
}

function formatCacheLine(usage: TokenUsage, value: Colorize, muted: Colorize): string | undefined {
const cacheRead = usageNumber(usage.inputCacheRead);
const cacheWrite = usageNumber(usage.inputCacheCreation);
if (cacheRead === 0 && cacheWrite === 0) return undefined;
return (
` ${muted('cache')} read ${value(formatTokenCount(cacheRead))} write ${value(
formatTokenCount(cacheWrite),
)}`
);
}

function buildSessionUsageSection(
usage: SessionUsage | undefined,
error: string | undefined,
Expand All @@ -78,23 +89,39 @@ function buildSessionUsageSection(
const lines: string[] = [];
let totalInput = 0;
let totalOutput = 0;
let totalCacheRead = 0;
let totalCacheWrite = 0;
for (const [model, row] of entries) {
const input = usageInputTotal(row);
const output = usageNumber(row.output);
const cacheRead = usageNumber(row.inputCacheRead);
const cacheWrite = usageNumber(row.inputCacheCreation);
totalInput += input;
totalOutput += output;
totalCacheRead += cacheRead;
totalCacheWrite += cacheWrite;
lines.push(
` ${muted(model)} input ${value(formatTokenCount(input))} output ${value(
formatTokenCount(output),
)} total ${value(formatTokenCount(input + output))}`,
);
const cacheLine = formatCacheLine(row, value, muted);
if (cacheLine !== undefined) lines.push(cacheLine);
}
if (entries.length > 1) {
const total: TokenUsage = {
inputOther: totalInput - totalCacheRead - totalCacheWrite,
output: totalOutput,
inputCacheRead: totalCacheRead,
inputCacheCreation: totalCacheWrite,
};
lines.push(
` ${muted('total')} input ${value(formatTokenCount(totalInput))} output ${value(
formatTokenCount(totalOutput),
)} total ${value(formatTokenCount(totalInput + totalOutput))}`,
);
const cacheLine = formatCacheLine(total, value, muted);
if (cacheLine !== undefined) lines.push(cacheLine);
}
return lines;
}
Expand Down
32 changes: 32 additions & 0 deletions apps/kimi-code/test/tui/components/messages/usage-panel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ describe('UsagePanelComponent', () => {
inputCacheCreation: 500,
output: 250,
},
'kimi-lite': {
inputOther: 200,
inputCacheRead: 0,
inputCacheCreation: 100,
output: 50,
},
},
} as never,
contextUsage: 0.25,
Expand All @@ -41,13 +47,39 @@ describe('UsagePanelComponent', () => {

expect(lines).toContain('Session usage');
expect(lines).toContain(' kimi input 2.0k output 250 total 2.3k');
expect(lines).toContain(' cache read 500 write 500');
expect(lines).toContain(' kimi-lite input 300 output 50 total 350');
expect(lines).toContain(' cache read 0 write 100');
expect(lines).toContain(' total input 2.3k output 300 total 2.6k');
expect(lines).toContain(' cache read 500 write 600');
expect(lines).toContain('Context window');
expect(lines.join('\n')).toContain('25.0%');
expect(lines).toContain('Plan usage');
expect(lines.join('\n')).toContain('20% used');
expect(lines.join('\n')).toContain('resets tomorrow');
});

it('omits cache rows when no cache tokens were recorded', () => {
const lines = buildUsageReportLines({
sessionUsage: {
byModel: {
kimi: {
inputOther: 1000,
inputCacheRead: 0,
inputCacheCreation: 0,
output: 250,
},
},
} as never,
contextUsage: 0,
contextTokens: 0,
maxContextTokens: 0,
}).map(strip);

expect(lines).toContain(' kimi input 1.0k output 250 total 1.3k');
expect(lines.join('\n')).not.toContain('cache');
});

it('wraps preformatted usage lines in a bordered panel', () => {
const component = new UsagePanelComponent(() => ['Session usage'], 'primary');
const output = component.render(80).map(strip);
Expand Down