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
58 changes: 53 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 @@ -1279,10 +1279,58 @@ 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 request metadata row)
// - 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 === "text" && !msg.text?.trim()) || msg.say === "reasoning"

@edelauna edelauna Jul 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These predicates are inline closures, and the spec's copies are hand-synced — which has now drifted twice (the codebase_search_result addition, and the api_req_finished comment on the spec). Two suggestions:

  • Export isIgnorableBetweenTargets/isBoundary from a small shared module so the spec imports the real predicates instead of copies.
  • Add a ChatView JSDOM test that feeds [readFile ask, api_req_started, readFile ask] and asserts one batched approval row renders instead of two — pins the UI tool ask batching fails when models insert API rows between tool calls #1004 regression where it lives (behavior-only per webview-ui/AGENTS.md, so Vitest rather than visual or e2e).

}

// 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.
// 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
116 changes: 0 additions & 116 deletions webview-ui/src/utils/__tests__/batchConsecutive.spec.ts

This file was deleted.

Loading
Loading