feat: Add conversation context token counter

- Add contextTokens to ApiMetrics interface
- Calculate context size using last API request's tokens
- Display context token count in TaskHeader below total tokens
- Use exact token counts instead of character estimation

This helps users track the total size of their conversation context,
which is useful for managing context window limits.
This commit is contained in:
MFPires
2025-01-27 23:02:25 -03:00
parent 86d4a10a9a
commit e668169ed9
3 changed files with 22 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ interface ApiMetrics {
totalCacheWrites?: number totalCacheWrites?: number
totalCacheReads?: number totalCacheReads?: number
totalCost: number totalCost: number
contextTokens: number // Total tokens in conversation (last message's tokensIn + tokensOut)
} }
/** /**
@@ -32,8 +33,14 @@ export function getApiMetrics(messages: ClineMessage[]): ApiMetrics {
totalCacheWrites: undefined, totalCacheWrites: undefined,
totalCacheReads: undefined, totalCacheReads: undefined,
totalCost: 0, totalCost: 0,
contextTokens: 0,
} }
// Find the last api_req_started message to get the context size
const lastApiReq = [...messages]
.reverse()
.find((message) => message.type === "say" && message.say === "api_req_started" && message.text)
messages.forEach((message) => { messages.forEach((message) => {
if (message.type === "say" && message.say === "api_req_started" && message.text) { if (message.type === "say" && message.say === "api_req_started" && message.text) {
try { try {
@@ -55,6 +62,11 @@ export function getApiMetrics(messages: ClineMessage[]): ApiMetrics {
if (typeof cost === "number") { if (typeof cost === "number") {
result.totalCost += cost result.totalCost += cost
} }
// If this is the last api request, use its tokens for context size
if (message === lastApiReq) {
result.contextTokens = (tokensIn || 0) + (tokensOut || 0)
}
} catch (error) { } catch (error) {
console.error("Error parsing JSON:", error) console.error("Error parsing JSON:", error)
} }

View File

@@ -915,6 +915,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
cacheWrites={apiMetrics.totalCacheWrites} cacheWrites={apiMetrics.totalCacheWrites}
cacheReads={apiMetrics.totalCacheReads} cacheReads={apiMetrics.totalCacheReads}
totalCost={apiMetrics.totalCost} totalCost={apiMetrics.totalCost}
contextTokens={apiMetrics.contextTokens}
onClose={handleTaskCloseButtonClick} onClose={handleTaskCloseButtonClick}
/> />
) : ( ) : (

View File

@@ -16,6 +16,7 @@ interface TaskHeaderProps {
cacheWrites?: number cacheWrites?: number
cacheReads?: number cacheReads?: number
totalCost: number totalCost: number
contextTokens: number
onClose: () => void onClose: () => void
} }
@@ -27,6 +28,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
cacheWrites, cacheWrites,
cacheReads, cacheReads,
totalCost, totalCost,
contextTokens,
onClose, onClose,
}) => { }) => {
const { apiConfiguration } = useExtensionState() const { apiConfiguration } = useExtensionState()
@@ -272,6 +274,13 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
{!isCostAvailable && <ExportButton />} {!isCostAvailable && <ExportButton />}
</div> </div>
<div style={{ display: "flex", alignItems: "center", gap: "4px", flexWrap: "wrap" }}>
<span style={{ fontWeight: "bold" }}>Context:</span>
<span style={{ display: "flex", alignItems: "center", gap: "3px" }}>
{formatLargeNumber(contextTokens || 0)}
</span>
</div>
{shouldShowPromptCacheInfo && (cacheReads !== undefined || cacheWrites !== undefined) && ( {shouldShowPromptCacheInfo && (cacheReads !== undefined || cacheWrites !== undefined) && (
<div style={{ display: "flex", alignItems: "center", gap: "4px", flexWrap: "wrap" }}> <div style={{ display: "flex", alignItems: "center", gap: "4px", flexWrap: "wrap" }}>
<span style={{ fontWeight: "bold" }}>Cache:</span> <span style={{ fontWeight: "bold" }}>Cache:</span>