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
64 changes: 59 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,7 @@ 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 type { ClineAsk, ClineSayTool, ClineMessage, ExtensionMessage, AudioType, SuggestionItem } from "@roo-code/types"
import { getCompletionCheckpoint, getSuggestionMode, isRetiredProvider } from "@roo-code/types"
Expand Down Expand Up @@ -1268,10 +1268,64 @@ 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)
// Messages that can be safely skipped over when batching tool asks.
// These are low-information or invisible messages that don't affect semantics:
// - api_req_started / api_req_finished (API request metadata rows)
// - empty text rows (partial streaming with no visible content)
// - reasoning rows (hidden from user by default)
const isIgnorableBetweenTargets = (msg: ClineMessage): boolean => {
if (msg.type !== "say") return false
return (
msg.say === "api_req_started" ||
msg.say === "api_req_finished" ||
(msg.say === "text" && !msg.text?.trim()) ||
msg.say === "reasoning"
)
}

// Semantic boundaries that stop batching. When we hit one of these,
// any current batch is finalized and the boundary message is preserved as-is:
// - user feedback / new user messages
// - visible assistant text (the model spoke to the user)
// - completion result (turn ended)
// - checkpoint saved
// - errors
const isBoundary = (msg: ClineMessage): boolean => {
if (msg.type !== "say") return false
return (
msg.say === "user_feedback" ||
msg.say === "user_feedback_diff" ||
(msg.say === "text" && !!msg.text?.trim()) ||
msg.say === "completion_result" ||
msg.say === "checkpoint_saved" ||
msg.say === "error" ||
msg.say === "condense_context" ||
msg.say === "codebase_search_result"
)
}

// Consolidate tool asks into batches, allowing ignorable messages between targets.
// Unlike batchConsecutive which only merges truly adjacent items, batchNearby
// skips over api_req_started/finished, 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
Loading
Loading