fix: calculate header percentage based on available input space (#11054)

Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
roomote[bot] 2026-01-28 15:29:34 -05:00 committed by GitHub
parent fe722dad23
commit 8640fd1472
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 75 additions and 3 deletions

View file

@ -282,9 +282,13 @@ const TaskHeader = ({
sideOffset={8}>
<span className="flex items-center gap-1.5">
{(() => {
const percentage = Math.round(
(((contextTokens || 0) + reservedForOutput) / contextWindow) * 100,
)
// Calculate percentage of available input space used
// Available input space = context window - reserved for output
const availableInputSpace = contextWindow - reservedForOutput
const percentage =
availableInputSpace > 0
? Math.round(((contextTokens || 0) / availableInputSpace) * 100)
: 0
return (
<>
<CircularProgress percentage={percentage} />

View file

@ -91,6 +91,26 @@ vi.mock("@roo/array", () => ({
},
}))
// Create a variable to hold the mock model info for useSelectedModel
let mockModelInfo: { contextWindow: number; maxTokens: number } | undefined = undefined
// Mock useSelectedModel hook
vi.mock("@/components/ui/hooks/useSelectedModel", () => ({
useSelectedModel: () => ({
provider: "anthropic",
id: "test-model",
info: mockModelInfo,
isLoading: false,
isError: false,
}),
}))
// Mock getModelMaxOutputTokens from @roo/api
let mockMaxOutputTokens = 0
vi.mock("@roo/api", () => ({
getModelMaxOutputTokens: () => mockMaxOutputTokens,
}))
describe("TaskHeader", () => {
const defaultProps: TaskHeaderProps = {
task: { type: "say", ts: Date.now(), text: "Test task", images: [] },
@ -402,4 +422,52 @@ describe("TaskHeader", () => {
expect(backButton?.querySelector("svg.lucide-arrow-left")).toBeInTheDocument()
})
})
describe("Context window percentage calculation", () => {
// The percentage should be calculated as:
// contextTokens / (contextWindow - reservedForOutput) * 100
// This represents the percentage of AVAILABLE input space used,
// not the percentage of the total context window.
beforeEach(() => {
// Set up mock model with known contextWindow
mockModelInfo = { contextWindow: 1000, maxTokens: 200 }
// Set up mock for getModelMaxOutputTokens to return reservedForOutput
mockMaxOutputTokens = 200
})
afterEach(() => {
// Reset mocks
mockModelInfo = undefined
mockMaxOutputTokens = 0
})
it("should calculate percentage based on available input space, not total context window", () => {
// With the formula: contextTokens / (contextWindow - reservedForOutput) * 100
// If contextTokens = 200, contextWindow = 1000, reservedForOutput = 200
// Then available input space = 1000 - 200 = 800
// Percentage = 200 / 800 * 100 = 25%
//
// Old (incorrect) formula would have been: (200 + 200) / 1000 * 100 = 40%
renderTaskHeader({ contextTokens: 200 })
// The percentage should be rendered in the collapsed header state
// Verify that 25% is displayed (correct formula) and NOT 40% (old incorrect formula)
expect(screen.getByText("25%")).toBeInTheDocument()
expect(screen.queryByText("40%")).not.toBeInTheDocument()
})
it("should handle edge case when available input space is zero", () => {
// When contextWindow equals reservedForOutput, available space is 0
// The percentage should be 0 to avoid division by zero
mockModelInfo = { contextWindow: 200, maxTokens: 200 }
mockMaxOutputTokens = 200
renderTaskHeader({ contextTokens: 100 })
// Should show 0% when available input space is 0
expect(screen.getByText("0%")).toBeInTheDocument()
})
})
})