mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Fixes #4948: Fix context tokens calculation causing doubling/halving
- Fixed getApiMetrics to only use tokensIn for context calculation - Previously was incorrectly adding tokensOut + cacheWrites + cacheReads - This caused oscillating context display when condensing occurred - Added comprehensive tests to verify the fix
This commit is contained in:
parent
2e2f83be60
commit
ad361faed7
2 changed files with 160 additions and 2 deletions
156
src/shared/__tests__/getApiMetrics.spec.ts
Normal file
156
src/shared/__tests__/getApiMetrics.spec.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import { getApiMetrics } from "../getApiMetrics"
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
||||
describe("getApiMetrics", () => {
|
||||
it("should calculate context tokens correctly using only tokensIn", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
text: JSON.stringify({
|
||||
request: "test request",
|
||||
tokensIn: 1000,
|
||||
tokensOut: 500,
|
||||
cacheWrites: 200,
|
||||
cacheReads: 100,
|
||||
cost: 0.01,
|
||||
}),
|
||||
ts: Date.now(),
|
||||
},
|
||||
]
|
||||
|
||||
const result = getApiMetrics(messages)
|
||||
|
||||
// Context tokens should only include tokensIn, not the sum of all token types
|
||||
expect(result.contextTokens).toBe(1000)
|
||||
expect(result.totalTokensIn).toBe(1000)
|
||||
expect(result.totalTokensOut).toBe(500)
|
||||
expect(result.totalCacheWrites).toBe(200)
|
||||
expect(result.totalCacheReads).toBe(100)
|
||||
expect(result.totalCost).toBe(0.01)
|
||||
})
|
||||
|
||||
it("should use newContextTokens from condense_context messages", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
text: JSON.stringify({
|
||||
tokensIn: 2000,
|
||||
tokensOut: 800,
|
||||
}),
|
||||
ts: Date.now() - 1000,
|
||||
},
|
||||
{
|
||||
type: "say",
|
||||
say: "condense_context",
|
||||
text: undefined,
|
||||
contextCondense: {
|
||||
summary: "Context was condensed",
|
||||
cost: 0.02,
|
||||
newContextTokens: 800,
|
||||
prevContextTokens: 2000,
|
||||
},
|
||||
ts: Date.now(),
|
||||
},
|
||||
]
|
||||
|
||||
const result = getApiMetrics(messages)
|
||||
|
||||
// Should use newContextTokens from the most recent condense_context message
|
||||
expect(result.contextTokens).toBe(800)
|
||||
expect(result.totalCost).toBe(0.02) // Only condense cost since api_req_started has no cost
|
||||
})
|
||||
|
||||
it("should handle multiple API requests and use the most recent for context", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
text: JSON.stringify({
|
||||
tokensIn: 1000,
|
||||
tokensOut: 400,
|
||||
cost: 0.01,
|
||||
}),
|
||||
ts: Date.now() - 2000,
|
||||
},
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
text: JSON.stringify({
|
||||
tokensIn: 1500,
|
||||
tokensOut: 600,
|
||||
cost: 0.015,
|
||||
}),
|
||||
ts: Date.now(),
|
||||
},
|
||||
]
|
||||
|
||||
const result = getApiMetrics(messages)
|
||||
|
||||
// Should use context tokens from the most recent API request
|
||||
expect(result.contextTokens).toBe(1500)
|
||||
expect(result.totalTokensIn).toBe(2500) // Sum of both requests
|
||||
expect(result.totalTokensOut).toBe(1000) // Sum of both requests
|
||||
expect(result.totalCost).toBe(0.025) // Sum of both costs
|
||||
})
|
||||
|
||||
it("should handle missing or invalid JSON gracefully", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
text: "invalid json",
|
||||
ts: Date.now(),
|
||||
},
|
||||
]
|
||||
|
||||
const result = getApiMetrics(messages)
|
||||
|
||||
expect(result.contextTokens).toBe(0)
|
||||
expect(result.totalTokensIn).toBe(0)
|
||||
expect(result.totalTokensOut).toBe(0)
|
||||
expect(result.totalCost).toBe(0)
|
||||
})
|
||||
|
||||
it("should prioritize condense_context over api_req_started when both exist", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
text: JSON.stringify({
|
||||
tokensIn: 2000,
|
||||
tokensOut: 800,
|
||||
}),
|
||||
ts: Date.now() - 1000,
|
||||
},
|
||||
{
|
||||
type: "say",
|
||||
say: "condense_context",
|
||||
text: undefined,
|
||||
contextCondense: {
|
||||
summary: "Context was condensed",
|
||||
cost: 0.02,
|
||||
newContextTokens: 1200,
|
||||
prevContextTokens: 2000,
|
||||
},
|
||||
ts: Date.now() - 500,
|
||||
},
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
text: JSON.stringify({
|
||||
tokensIn: 1300,
|
||||
tokensOut: 500,
|
||||
}),
|
||||
ts: Date.now(),
|
||||
},
|
||||
]
|
||||
|
||||
const result = getApiMetrics(messages)
|
||||
|
||||
// Should use the most recent message for context calculation
|
||||
// In this case, the most recent api_req_started
|
||||
expect(result.contextTokens).toBe(1300)
|
||||
})
|
||||
})
|
||||
|
|
@ -72,8 +72,10 @@ export function getApiMetrics(messages: ClineMessage[]) {
|
|||
if (message.type === "say" && message.say === "api_req_started" && message.text) {
|
||||
try {
|
||||
const parsedText: ParsedApiReqStartedTextType = JSON.parse(message.text)
|
||||
const { tokensIn, tokensOut, cacheWrites, cacheReads } = parsedText
|
||||
result.contextTokens = (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
|
||||
const { tokensIn } = parsedText
|
||||
// Only use tokensIn for context calculation - tokensOut, cacheWrites, and cacheReads
|
||||
// are not part of the context window usage
|
||||
result.contextTokens = tokensIn || 0
|
||||
} catch (error) {
|
||||
console.error("Error parsing JSON:", error)
|
||||
continue
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue