Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0f4cfec
[Condense] Condense messages with an LLM rather than truncating
canrobins13 May 13, 2025
8d6b71f
use actual messages rather than injecting them in the prompt
canrobins13 May 14, 2025
f7a9cd1
keep multiple messages
canrobins13 May 14, 2025
e43c50b
add image handling code
canrobins13 May 14, 2025
aa8e1d3
Update src/core/condense/index.ts
canrobins13 May 14, 2025
a97114a
add back sliding window as default implementation behind a flag
canrobins13 May 14, 2025
1248a93
use same timestamp as next message
canrobins13 May 14, 2025
11353c5
reverse logic
canrobins13 May 14, 2025
9d40c3b
wip on checkbox setting
canrobins13 May 14, 2025
cdf69e5
wip on checkbox
canrobins13 May 14, 2025
1765c6e
revert checkbox
canrobins13 May 14, 2025
63a82bf
use experimental setting instead
canrobins13 May 14, 2025
547d4d7
add image-cleaning.test.ts
canrobins13 May 14, 2025
2b453ee
add a test for condensing
canrobins13 May 14, 2025
7679ad3
update translations, thanks Roo
canrobins13 May 14, 2025
160932a
Revert "update translations, thanks Roo"
canrobins13 May 14, 2025
75166f3
update setting copy
canrobins13 May 14, 2025
00b27d3
use sliding window logic for when to summarize
canrobins13 May 14, 2025
39451dd
wip
canrobins13 May 14, 2025
cbcc575
wip
canrobins13 May 14, 2025
9c8ba3e
make param optional
canrobins13 May 14, 2025
4c62fa5
new tests
canrobins13 May 14, 2025
8ce7830
update translations, thanks Roo
canrobins13 May 14, 2025
76c274f
nit
canrobins13 May 14, 2025
a11badb
add changeset
canrobins13 May 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion evals/packages/types/src/roo-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ export type CommandExecutionStatus = z.infer<typeof commandExecutionStatusSchema
* ExperimentId
*/

export const experimentIds = ["powerSteering"] as const
export const experimentIds = ["autoCondenseContext", "powerSteering"] as const

export const experimentIdsSchema = z.enum(experimentIds)

Expand All @@ -308,6 +308,7 @@ export type ExperimentId = z.infer<typeof experimentIdsSchema>
*/

const experimentsSchema = z.object({
autoCondenseContext: z.boolean(),
powerSteering: z.boolean(),
})

Expand Down
336 changes: 336 additions & 0 deletions src/api/transform/__tests__/image-cleaning.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,336 @@
import { ApiHandler } from "../.."
import { ApiMessage } from "../../../core/task-persistence/apiMessages"
import { maybeRemoveImageBlocks } from "../image-cleaning"
import { ModelInfo } from "../../../shared/api"

describe("maybeRemoveImageBlocks", () => {
// Mock ApiHandler factory function
const createMockApiHandler = (supportsImages: boolean): ApiHandler => {
return {
getModel: jest.fn().mockReturnValue({
id: "test-model",
info: {
supportsImages,
} as ModelInfo,
}),
createMessage: jest.fn(),
countTokens: jest.fn(),
}
}

it("should handle empty messages array", () => {
const apiHandler = createMockApiHandler(true)
const messages: ApiMessage[] = []

const result = maybeRemoveImageBlocks(messages, apiHandler)

expect(result).toEqual([])
// No need to check if getModel was called since there are no messages to process
})

it("should not modify messages with no image blocks", () => {
const apiHandler = createMockApiHandler(true)
const messages: ApiMessage[] = [
{
role: "user",
content: "Hello, world!",
},
{
role: "assistant",
content: "Hi there!",
},
]

const result = maybeRemoveImageBlocks(messages, apiHandler)

expect(result).toEqual(messages)
// getModel is only called when content is an array, which is not the case here
})

it("should not modify messages with array content but no image blocks", () => {
const apiHandler = createMockApiHandler(true)
const messages: ApiMessage[] = [
{
role: "user",
content: [
{
type: "text",
text: "Hello, world!",
},
{
type: "text",
text: "How are you?",
},
],
},
]

const result = maybeRemoveImageBlocks(messages, apiHandler)

expect(result).toEqual(messages)
expect(apiHandler.getModel).toHaveBeenCalled()
})

it("should not modify image blocks when API handler supports images", () => {
const apiHandler = createMockApiHandler(true)
const messages: ApiMessage[] = [
{
role: "user",
content: [
{
type: "text",
text: "Check out this image:",
},
{
type: "image",
source: {
type: "base64",
media_type: "image/jpeg",
data: "base64-encoded-image-data",
},
},
],
},
]

const result = maybeRemoveImageBlocks(messages, apiHandler)

// Should not modify the messages since the API handler supports images
expect(result).toEqual(messages)
expect(apiHandler.getModel).toHaveBeenCalled()
})

it("should convert image blocks to text descriptions when API handler doesn't support images", () => {
const apiHandler = createMockApiHandler(false)
const messages: ApiMessage[] = [
{
role: "user",
content: [
{
type: "text",
text: "Check out this image:",
},
{
type: "image",
source: {
type: "base64",
media_type: "image/jpeg",
data: "base64-encoded-image-data",
},
},
],
},
]

const result = maybeRemoveImageBlocks(messages, apiHandler)

// Should convert image blocks to text descriptions
expect(result).toEqual([
{
role: "user",
content: [
{
type: "text",
text: "Check out this image:",
},
{
type: "text",
text: "[Referenced image in conversation]",
},
],
},
])
expect(apiHandler.getModel).toHaveBeenCalled()
})

it("should handle mixed content messages with multiple text and image blocks", () => {
const apiHandler = createMockApiHandler(false)
const messages: ApiMessage[] = [
{
role: "user",
content: [
{
type: "text",
text: "Here are some images:",
},
{
type: "image",
source: {
type: "base64",
media_type: "image/jpeg",
data: "image-data-1",
},
},
{
type: "text",
text: "And another one:",
},
{
type: "image",
source: {
type: "base64",
media_type: "image/png",
data: "image-data-2",
},
},
],
},
]

const result = maybeRemoveImageBlocks(messages, apiHandler)

// Should convert all image blocks to text descriptions
expect(result).toEqual([
{
role: "user",
content: [
{
type: "text",
text: "Here are some images:",
},
{
type: "text",
text: "[Referenced image in conversation]",
},
{
type: "text",
text: "And another one:",
},
{
type: "text",
text: "[Referenced image in conversation]",
},
],
},
])
expect(apiHandler.getModel).toHaveBeenCalled()
})

it("should handle multiple messages with image blocks", () => {
const apiHandler = createMockApiHandler(false)
const messages: ApiMessage[] = [
{
role: "user",
content: [
{
type: "text",
text: "Here's an image:",
},
{
type: "image",
source: {
type: "base64",
media_type: "image/jpeg",
data: "image-data-1",
},
},
],
},
{
role: "assistant",
content: "I see the image!",
},
{
role: "user",
content: [
{
type: "text",
text: "Here's another image:",
},
{
type: "image",
source: {
type: "base64",
media_type: "image/png",
data: "image-data-2",
},
},
],
},
]

const result = maybeRemoveImageBlocks(messages, apiHandler)

// Should convert all image blocks to text descriptions
expect(result).toEqual([
{
role: "user",
content: [
{
type: "text",
text: "Here's an image:",
},
{
type: "text",
text: "[Referenced image in conversation]",
},
],
},
{
role: "assistant",
content: "I see the image!",
},
{
role: "user",
content: [
{
type: "text",
text: "Here's another image:",
},
{
type: "text",
text: "[Referenced image in conversation]",
},
],
},
])
expect(apiHandler.getModel).toHaveBeenCalled()
})

it("should preserve additional message properties", () => {
const apiHandler = createMockApiHandler(false)
const messages: ApiMessage[] = [
{
role: "user",
content: [
{
type: "text",
text: "Here's an image:",
},
{
type: "image",
source: {
type: "base64",
media_type: "image/jpeg",
data: "image-data",
},
},
],
ts: 1620000000000,
isSummary: true,
},
]

const result = maybeRemoveImageBlocks(messages, apiHandler)

// Should convert image blocks to text descriptions while preserving additional properties
expect(result).toEqual([
{
role: "user",
content: [
{
type: "text",
text: "Here's an image:",
},
{
type: "text",
text: "[Referenced image in conversation]",
},
],
ts: 1620000000000,
isSummary: true,
},
])
expect(apiHandler.getModel).toHaveBeenCalled()
})
})
28 changes: 28 additions & 0 deletions src/api/transform/image-cleaning.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ApiHandler } from ".."
import { ApiMessage } from "../../core/task-persistence/apiMessages"

/* Removes image blocks from messages if they are not supported by the Api Handler */
export function maybeRemoveImageBlocks(messages: ApiMessage[], apiHandler: ApiHandler): ApiMessage[] {
return messages.map((message) => {
// Handle array content (could contain image blocks).
let { content } = message
if (Array.isArray(content)) {
if (!apiHandler.getModel().info.supportsImages) {
// Convert image blocks to text descriptions.
content = content.map((block) => {
if (block.type === "image") {
// Convert image blocks to text descriptions.
// Note: We can't access the actual image content/url due to API limitations,
// but we can indicate that an image was present in the conversation.
return {
type: "text",
text: "[Referenced image in conversation]",
}
}
return block
})
}
}
return { ...message, content }
})
}
Loading