mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Fix sliding window context management handling of images and get more accurate token estimates
This commit is contained in:
86
package-lock.json
generated
86
package-lock.json
generated
@@ -1,25 +1,28 @@
|
||||
{
|
||||
"name": "claude-dev",
|
||||
"version": "1.4.11",
|
||||
"version": "1.4.12",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-dev",
|
||||
"version": "1.4.11",
|
||||
"version": "1.4.12",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/bedrock-sdk": "^0.10.2",
|
||||
"@anthropic-ai/sdk": "^0.26.0",
|
||||
"@anthropic-ai/tokenizer": "^0.0.4",
|
||||
"@kodu-ai/cloud-api": "^1.0.1",
|
||||
"@types/clone-deep": "^4.0.4",
|
||||
"@vscode/codicons": "^0.0.36",
|
||||
"axios": "^1.7.4",
|
||||
"clone-deep": "^4.0.1",
|
||||
"default-shell": "^2.2.0",
|
||||
"delay": "^6.0.0",
|
||||
"diff": "^5.2.0",
|
||||
"execa": "^9.3.0",
|
||||
"globby": "^14.0.2",
|
||||
"image-size": "^1.1.1",
|
||||
"openai": "^4.54.0",
|
||||
"os-name": "^6.0.0",
|
||||
"p-wait-for": "^5.0.2",
|
||||
@@ -4528,6 +4531,11 @@
|
||||
"https://trpc.io/sponsor"
|
||||
]
|
||||
},
|
||||
"node_modules/@types/clone-deep": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/clone-deep/-/clone-deep-4.0.4.tgz",
|
||||
"integrity": "sha512-vXh6JuuaAha6sqEbJueYdh5zNBPPgG1OYumuz2UvLvriN6ABHDSW8ludREGWJb1MLIzbwZn4q4zUbUCerJTJfA=="
|
||||
},
|
||||
"node_modules/@types/diff": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.1.tgz",
|
||||
@@ -5401,6 +5409,19 @@
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/clone-deep": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
|
||||
"integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
|
||||
"dependencies": {
|
||||
"is-plain-object": "^2.0.4",
|
||||
"kind-of": "^6.0.2",
|
||||
"shallow-clone": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -6844,6 +6865,20 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/image-size": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz",
|
||||
"integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==",
|
||||
"dependencies": {
|
||||
"queue": "6.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"image-size": "bin/image-size.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.x"
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
@@ -6894,7 +6929,6 @@
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/internal-slot": {
|
||||
@@ -7142,6 +7176,17 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
||||
"dependencies": {
|
||||
"isobject": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-regex": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||
@@ -7285,6 +7330,14 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/isobject": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-coverage": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
||||
@@ -7420,6 +7473,14 @@
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
@@ -8642,6 +8703,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/queue": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
|
||||
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
|
||||
"dependencies": {
|
||||
"inherits": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@@ -9048,6 +9117,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shallow-clone": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
|
||||
"integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
|
||||
"dependencies": {
|
||||
"kind-of": "^6.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
||||
@@ -136,13 +136,16 @@
|
||||
"@anthropic-ai/sdk": "^0.26.0",
|
||||
"@anthropic-ai/tokenizer": "^0.0.4",
|
||||
"@kodu-ai/cloud-api": "^1.0.1",
|
||||
"@types/clone-deep": "^4.0.4",
|
||||
"@vscode/codicons": "^0.0.36",
|
||||
"axios": "^1.7.4",
|
||||
"clone-deep": "^4.0.1",
|
||||
"default-shell": "^2.2.0",
|
||||
"delay": "^6.0.0",
|
||||
"diff": "^5.2.0",
|
||||
"execa": "^9.3.0",
|
||||
"globby": "^14.0.2",
|
||||
"image-size": "^1.1.1",
|
||||
"openai": "^4.54.0",
|
||||
"os-name": "^6.0.0",
|
||||
"p-wait-for": "^5.0.2",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { countTokens } from "@anthropic-ai/tokenizer"
|
||||
import { Buffer } from "buffer"
|
||||
import sizeOf from "image-size"
|
||||
import cloneDeep from "clone-deep"
|
||||
|
||||
export function slidingWindowContextManagement(
|
||||
contextWindow: number,
|
||||
@@ -18,28 +21,31 @@ export function slidingWindowContextManagement(
|
||||
}
|
||||
|
||||
// If over limit, remove messages starting from the third message onwards (task and claude's step-by-step thought process are important to keep in context)
|
||||
const newMessages = [...messages]
|
||||
const newMessages = cloneDeep(messages) // since we're manipulating nested objects and arrays, need to deep clone to prevent mutating original history
|
||||
let index = 2
|
||||
while (totalMessageTokens > availableTokens && index < newMessages.length) {
|
||||
const messageToEmpty = newMessages[index]
|
||||
const originalTokens = countMessageTokens(messageToEmpty)
|
||||
// Empty the content of the message (messages must be in a specific order so we can't just remove)
|
||||
if (typeof messageToEmpty.content === "string") {
|
||||
messageToEmpty.content = ""
|
||||
messageToEmpty.content = "(truncated due to context limits)"
|
||||
} else if (Array.isArray(messageToEmpty.content)) {
|
||||
messageToEmpty.content = messageToEmpty.content.map((item) => {
|
||||
if (typeof item === "string") {
|
||||
return {
|
||||
type: "text",
|
||||
text: "(truncated due to context window)",
|
||||
text: "(truncated due to context limits)",
|
||||
} as Anthropic.Messages.TextBlockParam
|
||||
} else if (item.type === "text") {
|
||||
return {
|
||||
type: "text",
|
||||
text: "(truncated due to context window)",
|
||||
text: "(truncated due to context limits)",
|
||||
} as Anthropic.Messages.TextBlockParam
|
||||
} else if (item.type === "image") {
|
||||
return { ...item, source: { type: "base64", data: "" } } as Anthropic.Messages.ImageBlockParam
|
||||
return {
|
||||
type: "text",
|
||||
text: "(image removed due to context limits)",
|
||||
} as Anthropic.Messages.TextBlockParam
|
||||
} else if (item.type === "tool_use") {
|
||||
return { ...item, input: {} } as Anthropic.Messages.ToolUseBlockParam
|
||||
} else if (item.type === "tool_result") {
|
||||
@@ -48,9 +54,9 @@ export function slidingWindowContextManagement(
|
||||
content: Array.isArray(item.content)
|
||||
? item.content.map((contentItem) =>
|
||||
contentItem.type === "text"
|
||||
? { ...contentItem, text: "(truncated due to context window)" }
|
||||
? { type: "text", text: "(truncated due to context limits)" }
|
||||
: contentItem.type === "image"
|
||||
? { ...contentItem, source: { type: "base64", data: "" } }
|
||||
? { type: "text", text: "(image removed due to context limits)" }
|
||||
: contentItem
|
||||
)
|
||||
: "",
|
||||
@@ -69,7 +75,50 @@ export function slidingWindowContextManagement(
|
||||
function countMessageTokens(message: Anthropic.Messages.MessageParam): number {
|
||||
if (typeof message.content === "string") {
|
||||
return countTokens(message.content)
|
||||
} else if (Array.isArray(message.content)) {
|
||||
return message.content.reduce((sum, item) => {
|
||||
if (typeof item === "string") {
|
||||
return sum + countTokens(item)
|
||||
} else if (item.type === "text") {
|
||||
return sum + countTokens(item.text)
|
||||
} else if (item.type === "image") {
|
||||
return sum + estimateImageTokens(item.source.data)
|
||||
} else if (item.type === "tool_use") {
|
||||
return sum + countTokens(JSON.stringify(item.input))
|
||||
} else if (item.type === "tool_result") {
|
||||
if (Array.isArray(item.content)) {
|
||||
return (
|
||||
sum +
|
||||
item.content.reduce((contentSum, contentItem) => {
|
||||
if (contentItem.type === "text") {
|
||||
return contentSum + countTokens(contentItem.text)
|
||||
} else if (contentItem.type === "image") {
|
||||
return contentSum + estimateImageTokens(contentItem.source.data)
|
||||
}
|
||||
return contentSum + countTokens(JSON.stringify(contentItem))
|
||||
}, 0)
|
||||
)
|
||||
} else {
|
||||
return sum + countTokens(item.content || "")
|
||||
}
|
||||
} else {
|
||||
return sum + countTokens(JSON.stringify(item))
|
||||
}
|
||||
}, 0)
|
||||
} else {
|
||||
return countTokens(JSON.stringify(message.content))
|
||||
}
|
||||
}
|
||||
|
||||
function estimateImageTokens(base64: string): number {
|
||||
const base64Data = base64.split(";base64,").pop()
|
||||
if (base64Data) {
|
||||
const buffer = Buffer.from(base64Data, "base64")
|
||||
const dimensions = sizeOf(buffer)
|
||||
if (dimensions.width && dimensions.height) {
|
||||
// "you can estimate the number of tokens used through this algorithm: tokens = (width px * height px)/750"
|
||||
return Math.ceil((dimensions.width * dimensions.height) / 750)
|
||||
}
|
||||
}
|
||||
return countTokens(base64)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user