mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
fix: handle VS Code LM API token count of 4 by recalculating with string content
- When VS Code LM API returns token count of 4 for LanguageModelChatMessage, convert to string and recalculate - This addresses issue #6290 where context size was not being calculated correctly - Added comprehensive test coverage for the new behavior - Updated instanceof check to work with mocked objects in tests
This commit is contained in:
parent
342ee70fb4
commit
ce29a5324d
2 changed files with 158 additions and 1 deletions
|
|
@ -300,4 +300,132 @@ describe("VsCodeLmHandler", () => {
|
|||
await expect(promise).rejects.toThrow("VSCode LM completion error: Completion failed")
|
||||
})
|
||||
})
|
||||
|
||||
describe("countTokens", () => {
|
||||
beforeEach(() => {
|
||||
const mockModel = { ...mockLanguageModelChat }
|
||||
;(vscode.lm.selectChatModels as Mock).mockResolvedValueOnce([mockModel])
|
||||
handler["client"] = mockLanguageModelChat
|
||||
handler["currentRequestCancellation"] = new vscode.CancellationTokenSource()
|
||||
})
|
||||
|
||||
it("should count tokens for text content", async () => {
|
||||
const content: Anthropic.Messages.ContentBlockParam[] = [
|
||||
{ type: "text", text: "Hello world" },
|
||||
{ type: "text", text: "How are you?" },
|
||||
]
|
||||
|
||||
mockLanguageModelChat.countTokens.mockResolvedValue(15)
|
||||
|
||||
const result = await handler.countTokens(content)
|
||||
expect(result).toBe(15)
|
||||
expect(mockLanguageModelChat.countTokens).toHaveBeenCalledWith(
|
||||
"Hello worldHow are you?",
|
||||
expect.any(Object),
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle image content with placeholder", async () => {
|
||||
const content: Anthropic.Messages.ContentBlockParam[] = [
|
||||
{ type: "text", text: "Look at this:" },
|
||||
{ type: "image", source: { type: "base64", media_type: "image/png", data: "base64data" } },
|
||||
]
|
||||
|
||||
mockLanguageModelChat.countTokens.mockResolvedValue(10)
|
||||
|
||||
const result = await handler.countTokens(content)
|
||||
expect(result).toBe(10)
|
||||
expect(mockLanguageModelChat.countTokens).toHaveBeenCalledWith("Look at this:[IMAGE]", expect.any(Object))
|
||||
})
|
||||
})
|
||||
|
||||
describe("internalCountTokens", () => {
|
||||
beforeEach(() => {
|
||||
const mockModel = { ...mockLanguageModelChat }
|
||||
;(vscode.lm.selectChatModels as Mock).mockResolvedValueOnce([mockModel])
|
||||
handler["client"] = mockLanguageModelChat
|
||||
handler["currentRequestCancellation"] = new vscode.CancellationTokenSource()
|
||||
})
|
||||
|
||||
it("should count tokens for string input", async () => {
|
||||
mockLanguageModelChat.countTokens.mockResolvedValue(20)
|
||||
|
||||
const result = await handler["internalCountTokens"]("Test string")
|
||||
expect(result).toBe(20)
|
||||
expect(mockLanguageModelChat.countTokens).toHaveBeenCalledWith("Test string", expect.any(Object))
|
||||
})
|
||||
|
||||
it("should handle LanguageModelChatMessage with normal token count", async () => {
|
||||
const message = vscode.LanguageModelChatMessage.User("Hello")
|
||||
mockLanguageModelChat.countTokens.mockResolvedValue(10)
|
||||
|
||||
const result = await handler["internalCountTokens"](message)
|
||||
expect(result).toBe(10)
|
||||
expect(mockLanguageModelChat.countTokens).toHaveBeenCalledTimes(1)
|
||||
expect(mockLanguageModelChat.countTokens).toHaveBeenCalledWith(message, expect.any(Object))
|
||||
})
|
||||
|
||||
it("should recalculate when LanguageModelChatMessage returns token count of 4", async () => {
|
||||
const message = vscode.LanguageModelChatMessage.User(
|
||||
"This is a longer message that should have more than 4 tokens",
|
||||
)
|
||||
|
||||
// First call returns 4 (the problematic value)
|
||||
// Second call returns the correct count after string conversion
|
||||
mockLanguageModelChat.countTokens.mockResolvedValueOnce(4).mockResolvedValueOnce(25)
|
||||
|
||||
const result = await handler["internalCountTokens"](message)
|
||||
expect(result).toBe(25)
|
||||
expect(mockLanguageModelChat.countTokens).toHaveBeenCalledTimes(2)
|
||||
|
||||
// First call with the message object
|
||||
expect(mockLanguageModelChat.countTokens).toHaveBeenNthCalledWith(1, message, expect.any(Object))
|
||||
|
||||
// Second call with the extracted text
|
||||
expect(mockLanguageModelChat.countTokens).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"This is a longer message that should have more than 4 tokens",
|
||||
expect.any(Object),
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle LanguageModelChatMessage with array content when token count is 4", async () => {
|
||||
const textPart = new vscode.LanguageModelTextPart("Part 1")
|
||||
const textPart2 = new vscode.LanguageModelTextPart(" Part 2")
|
||||
const message = {
|
||||
role: "user",
|
||||
content: [textPart, textPart2],
|
||||
}
|
||||
|
||||
mockLanguageModelChat.countTokens.mockResolvedValueOnce(4).mockResolvedValueOnce(15)
|
||||
|
||||
const result = await handler["internalCountTokens"](message as any)
|
||||
expect(result).toBe(15)
|
||||
expect(mockLanguageModelChat.countTokens).toHaveBeenCalledTimes(2)
|
||||
expect(mockLanguageModelChat.countTokens).toHaveBeenNthCalledWith(2, "Part 1 Part 2", expect.any(Object))
|
||||
})
|
||||
|
||||
it("should return 0 when no client is available", async () => {
|
||||
handler["client"] = null
|
||||
|
||||
const result = await handler["internalCountTokens"]("Test")
|
||||
expect(result).toBe(0)
|
||||
expect(mockLanguageModelChat.countTokens).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should return 0 when no cancellation token is available", async () => {
|
||||
handler["currentRequestCancellation"] = null
|
||||
|
||||
const result = await handler["internalCountTokens"]("Test")
|
||||
expect(result).toBe(0)
|
||||
expect(mockLanguageModelChat.countTokens).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle errors gracefully", async () => {
|
||||
mockLanguageModelChat.countTokens.mockRejectedValue(new Error("Token counting failed"))
|
||||
|
||||
const result = await handler["internalCountTokens"]("Test")
|
||||
expect(result).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -225,13 +225,42 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
|
|||
|
||||
if (typeof text === "string") {
|
||||
tokenCount = await this.client.countTokens(text, this.currentRequestCancellation.token)
|
||||
} else if (text instanceof vscode.LanguageModelChatMessage) {
|
||||
} else if (text && typeof text === "object" && "role" in text && "content" in text) {
|
||||
// For chat messages, ensure we have content
|
||||
if (!text.content || (Array.isArray(text.content) && text.content.length === 0)) {
|
||||
console.debug("Roo Code <Language Model API>: Empty chat message content")
|
||||
return 0
|
||||
}
|
||||
tokenCount = await this.client.countTokens(text, this.currentRequestCancellation.token)
|
||||
|
||||
// Special handling: when tokenCount equals 4 for LanguageModelChatMessage,
|
||||
// convert to string and recalculate
|
||||
if (tokenCount === 4) {
|
||||
console.debug(
|
||||
"Roo Code <Language Model API>: Token count is 4, converting message to string for recalculation",
|
||||
)
|
||||
|
||||
// Extract text content from the message
|
||||
let textContent = ""
|
||||
if (typeof text.content === "string") {
|
||||
textContent = text.content
|
||||
} else if (Array.isArray(text.content)) {
|
||||
// Handle array of content parts
|
||||
for (const part of text.content) {
|
||||
if (part instanceof vscode.LanguageModelTextPart) {
|
||||
textContent += part.value
|
||||
} else if (typeof part === "string") {
|
||||
textContent += part
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recalculate tokens using the extracted text
|
||||
if (textContent) {
|
||||
tokenCount = await this.client.countTokens(textContent, this.currentRequestCancellation.token)
|
||||
console.debug(`Roo Code <Language Model API>: Recalculated token count: ${tokenCount}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn("Roo Code <Language Model API>: Invalid input type for token counting")
|
||||
return 0
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue