Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 23 additions & 5 deletions webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { LRUCache } from "lru-cache"
import { useDebounceEffect } from "@src/utils/useDebounceEffect"
import { appendImages } from "@src/utils/imageUtils"
import { getCostBreakdownIfNeeded } from "@src/utils/costFormatting"
import { batchConsecutive } from "@src/utils/batchConsecutive"
import { batchNearby } from "@src/utils/batchNearby"
import { isBoundary, isIgnorableBetweenTargets } from "@src/utils/chatBatchingPredicates"

import type { ClineAsk, ClineSayTool, ClineMessage, ExtensionMessage, AudioType, SuggestionItem } from "@roo-code/types"
import { getCompletionCheckpoint, getSuggestionMode, isRetiredProvider } from "@roo-code/types"
Expand Down Expand Up @@ -1279,10 +1280,27 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
}
}

// Consolidate consecutive ask messages into batches
const readFileBatched = batchConsecutive(filtered, isReadFileAsk, synthesizeReadFileBatch)
const listFilesBatched = batchConsecutive(readFileBatched, isListFilesAsk, synthesizeListFilesBatch)
const result = batchConsecutive(listFilesBatched, isEditFileAsk, synthesizeEditFileBatch)
// Consolidate tool asks into batches, allowing ignorable messages between targets.
// batchNearby skips over api_req_started, empty text rows, and reasoning rows that
// models like qwen insert between tool calls during streaming.
const readFileBatched = batchNearby(filtered, {
isTarget: isReadFileAsk,
isIgnorableBetweenTargets,
isBoundary,
synthesize: synthesizeReadFileBatch,
})
const listFilesBatched = batchNearby(readFileBatched, {
isTarget: isListFilesAsk,
isIgnorableBetweenTargets,
isBoundary,
synthesize: synthesizeListFilesBatch,
})
const result = batchNearby(listFilesBatched, {
isTarget: isEditFileAsk,
isIgnorableBetweenTargets,
isBoundary,
synthesize: synthesizeEditFileBatch,
})

if (isCondensing) {
result.push({
Expand Down
70 changes: 70 additions & 0 deletions webview-ui/src/components/chat/__tests__/ChatView.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const mockVirtuosoState = vi.hoisted(() => ({
defaultItemHeight?: number
increaseViewportBy?: number | { top?: number; bottom?: number }
} | null,
lastData: [] as ClineMessage[],
}))

// Define minimal types needed for testing
Expand Down Expand Up @@ -111,6 +112,7 @@ vi.mock("react-virtuoso", () => ({
defaultItemHeight,
increaseViewportBy,
}
mockVirtuosoState.lastData = data

return (
<div data-testid="virtuoso-item-list">
Expand Down Expand Up @@ -287,6 +289,22 @@ vi.mock("@vscode/webview-ui-toolkit/react", () => ({
</button>
)
},
VSCodeCheckbox: function MockVSCodeCheckbox({
children,
checked,
onChange,
}: {
children: React.ReactNode
checked?: boolean
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
}) {
return (
<label>
<input type="checkbox" checked={checked} onChange={onChange} />
{children}
</label>
)
},
VSCodeTextField: function MockVSCodeTextField({
value,
onInput,
Expand All @@ -308,6 +326,9 @@ vi.mock("@vscode/webview-ui-toolkit/react", () => ({
VSCodeLink: function MockVSCodeLink({ children, href }: { children: React.ReactNode; href?: string }) {
return <a href={href}>{children}</a>
},
VSCodeProgressRing: function MockVSCodeProgressRing() {
return <div data-testid="vscode-progress-ring" />
},
}))

// Mock window.postMessage to trigger state hydration
Expand Down Expand Up @@ -349,6 +370,55 @@ const renderChatView = (props: Partial<ChatViewProps> = {}) => {
)
}

describe("ChatView - Tool Batching Tests", () => {
beforeEach(() => vi.clearAllMocks())

it("batches readFile asks separated by an API request row", async () => {
renderChatView()

mockPostMessage({
clineMessages: [
{
type: "say",
say: "task",
ts: 1,
text: "Initial task",
},
{
type: "ask",
ask: "tool",
ts: 2,
text: JSON.stringify({ tool: "readFile", path: "a.ts" }),
},
{
type: "say",
say: "api_req_started",
ts: 3,
text: JSON.stringify({ apiProtocol: "anthropic" }),
},
{
type: "ask",
ask: "tool",
ts: 4,
text: JSON.stringify({ tool: "readFile", path: "b.ts" }),
},
],
})

await waitFor(() => {
const toolRows = mockVirtuosoState.lastData.filter(
(message) => message.type === "ask" && message.ask === "tool",
)
const [toolRow] = toolRows

expect(toolRows).toHaveLength(1)
expect(toolRow?.text).toContain('"batchFiles"')
expect(toolRow?.text).toContain('"path":"a.ts"')
expect(toolRow?.text).toContain('"path":"b.ts"')
})
})
})

describe("ChatView - Sound Playing Tests", () => {
beforeEach(() => vi.clearAllMocks())

Expand Down
116 changes: 0 additions & 116 deletions webview-ui/src/utils/__tests__/batchConsecutive.spec.ts

This file was deleted.

Loading
Loading