fix(web): clarify token usage labels in message details (#1062)

This commit is contained in:
Ananovo
2026-07-18 12:19:03 +08:00
committed by GitHub
parent d809fca433
commit bc2545d303
3 changed files with 7 additions and 13 deletions
@@ -58,7 +58,7 @@ describe('MessageActions', () => {
expect(screen.getByRole('button', { name: 'Message details' })).toBeTruthy()
expect(screen.getByText('Duration: 1.3s')).toBeTruthy()
expect(screen.getByText('Model: gpt-5.2-codex')).toBeTruthy()
expect(screen.getByText('Usage: 125 billable tokens (100 in / 25 out)')).toBeTruthy()
expect(screen.getByText('Tokens: 125 total (100 in / 25 out)')).toBeTruthy()
expect(screen.queryByText(/^Invoke:/)).toBeNull()
})
@@ -41,12 +41,12 @@ describe('buildMessageMetadataLabels', () => {
expect(buildMessageMetadataLabels({})).toEqual([])
})
it('labels token totals as billable to clarify that cache I/O is intentionally excluded', () => {
it('renders the token total and input/output breakdown without billing claims', () => {
const parts = buildMessageMetadataLabels({
usage: { input_tokens: 100, output_tokens: 200 }
})
expect(parts.some(p => /\bbillable tokens\b/.test(p))).toBe(true)
expect(parts.some(p => p.includes('300 billable tokens (100 in / 200 out)'))).toBe(true)
expect(parts).toContain('Tokens: 300 total (100 in / 200 out)')
expect(parts.some(p => /\bbillable\b/.test(p))).toBe(false)
})
it('does not drop a Duration line when durationMs is exactly 0', () => {
@@ -83,7 +83,7 @@ describe('buildMessageMetadataLabels', () => {
expect(parts).toEqual([
'Duration: 1.2s',
'Model: claude-sonnet-4-6',
'Usage: 22 billable tokens (3 in / 19 out)'
'Tokens: 22 total (3 in / 19 out)'
])
})
@@ -95,8 +95,7 @@ describe('buildMessageMetadataLabels', () => {
})
expect(parts).toContain('Models: claude-sonnet-4-6, claude-haiku-4-5-20251001')
expect(parts.some(p => p.startsWith('Model:'))).toBe(false)
expect(parts).toContain('Total: 300 billable tokens (100 in / 200 out)')
expect(parts.some(p => p.startsWith('Usage:'))).toBe(false)
expect(parts).toContain('Tokens: 300 total (100 in / 200 out)')
expect(parts).toContain('3 turns')
})
@@ -37,14 +37,9 @@ export function buildMessageMetadataLabels({ durationMs, usage, model, turnCount
}
if (usage) {
// "Billable" because cache_read tokens are not part of the input
// figure used for billing; surfacing only input + output here is
// intentional. If we ever surface cache I/O, keep this label and
// add a separate `Cache:` line.
const total = usage.input_tokens + usage.output_tokens
const formatToken = (n: number) => n.toLocaleString()
const usageLabel = isAggregated ? 'Total' : 'Usage'
parts.push(`${usageLabel}: ${formatToken(total)} billable tokens (${formatToken(usage.input_tokens)} in / ${formatToken(usage.output_tokens)} out)`)
parts.push(`Tokens: ${formatToken(total)} total (${formatToken(usage.input_tokens)} in / ${formatToken(usage.output_tokens)} out)`)
}
if (isAggregated) {