Skip to content

Commit 643da52

Browse files
committed
feat: Add token statistics display to API requests (fixes #7740)
1 parent e8deedd commit 643da52

File tree

2 files changed

+86
-3
lines changed

2 files changed

+86
-3
lines changed

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
1717
import { vscode } from "@src/utils/vscode"
1818
import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanumeric"
1919
import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
20+
import { formatTokenStats } from "@src/utils/formatTokens"
2021
import { Button } from "@src/components/ui"
2122

2223
import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
@@ -180,13 +181,20 @@ export const ChatRowContent = ({
180181
vscode.postMessage({ type: "selectImages", context: "edit", messageTs: message.ts })
181182
}, [message.ts])
182183

183-
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
184+
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage, tokensIn, tokensOut, cacheReads] = useMemo(() => {
184185
if (message.text !== null && message.text !== undefined && message.say === "api_req_started") {
185186
const info = safeJsonParse<ClineApiReqInfo>(message.text)
186-
return [info?.cost, info?.cancelReason, info?.streamingFailedMessage]
187+
return [
188+
info?.cost,
189+
info?.cancelReason,
190+
info?.streamingFailedMessage,
191+
info?.tokensIn,
192+
info?.tokensOut,
193+
info?.cacheReads,
194+
]
187195
}
188196

189-
return [undefined, undefined, undefined]
197+
return [undefined, undefined, undefined, undefined, undefined, undefined]
190198
}, [message.text, message.say])
191199

192200
// When resuming task, last wont be api_req_failed but a resume_task
@@ -1093,6 +1101,9 @@ export const ChatRowContent = ({
10931101
/>
10941102
)
10951103
case "api_req_started":
1104+
const tokenStats = formatTokenStats(tokensIn, tokensOut, cacheReads)
1105+
const hasTokenData = tokensIn !== undefined || tokensOut !== undefined
1106+
10961107
return (
10971108
<>
10981109
<div
@@ -1118,6 +1129,26 @@ export const ChatRowContent = ({
11181129
style={{ opacity: cost !== null && cost !== undefined && cost > 0 ? 1 : 0 }}>
11191130
${Number(cost || 0)?.toFixed(4)}
11201131
</VSCodeBadge>
1132+
{hasTokenData && (
1133+
<div
1134+
className="flex items-center gap-1 flex-wrap"
1135+
style={{ opacity: hasTokenData ? 1 : 0 }}>
1136+
<span
1137+
style={{
1138+
fontSize: "12px",
1139+
color: "var(--vscode-descriptionForeground)",
1140+
}}>
1141+
{tokenStats.input}
1142+
</span>
1143+
<span
1144+
style={{
1145+
fontSize: "12px",
1146+
color: "var(--vscode-descriptionForeground)",
1147+
}}>
1148+
{tokenStats.output}
1149+
</span>
1150+
</div>
1151+
)}
11211152
</div>
11221153
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
11231154
</div>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Format token count for display
3+
* @param count - The token count to format
4+
* @returns Formatted string (e.g., "1.2k" for 1200)
5+
*/
6+
export function formatTokenCount(count: number | undefined): string {
7+
if (count === undefined || count === 0) {
8+
return "0"
9+
}
10+
11+
if (count < 1000) {
12+
return count.toString()
13+
}
14+
15+
// Format as k (thousands) with one decimal place
16+
const thousands = count / 1000
17+
if (thousands < 10) {
18+
// For values less than 10k, show one decimal place
19+
return `${thousands.toFixed(1)}k`
20+
} else {
21+
// For values 10k and above, show no decimal places
22+
return `${Math.round(thousands)}k`
23+
}
24+
}
25+
26+
/**
27+
* Format token statistics for display
28+
* @param tokensIn - Input tokens
29+
* @param tokensOut - Output tokens
30+
* @param cacheReads - Cache read tokens (optional)
31+
* @returns Formatted string for display
32+
*/
33+
export function formatTokenStats(
34+
tokensIn?: number,
35+
tokensOut?: number,
36+
cacheReads?: number,
37+
): { input: string; output: string } {
38+
let inputDisplay = formatTokenCount(tokensIn)
39+
40+
// Add cache reads in parentheses if they exist
41+
if (cacheReads && cacheReads > 0) {
42+
const cacheDisplay = formatTokenCount(cacheReads)
43+
inputDisplay = `${inputDisplay} (${cacheDisplay} cache)`
44+
}
45+
46+
const outputDisplay = formatTokenCount(tokensOut)
47+
48+
return {
49+
input: inputDisplay,
50+
output: outputDisplay,
51+
}
52+
}

0 commit comments

Comments
 (0)