mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-01 18:13:03 +08:00
fix(chat): group continuous same-tool runs more consistently
Consecutive tool calls (Edit, Read, Grep, etc.) grouped inconsistently: - The group threshold was 3, so a run of only 2 calls stayed ungrouped while a run of 3 collapsed — making two back-to-back edits look different from three. - A run was broken by any interleaved message, including ones that render nothing (reasoning hidden when showThinking is off). Providers like Codex interleave hidden reasoning between tool calls, so visually continuous edits intermittently failed to group. Lower TOOL_GROUP_THRESHOLD to 2 and skip non-rendered messages when extending a run, so any 2+ consecutive same-tool calls collapse reliably. ChatMessagesPane now passes showThinking into groupConsecutiveTools.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { ChatMessage } from '../types/types';
|
||||
|
||||
export const TOOL_GROUP_THRESHOLD = 3;
|
||||
export const TOOL_GROUP_THRESHOLD = 2;
|
||||
|
||||
export interface ToolGroupItem {
|
||||
_isGroup: true;
|
||||
@@ -19,7 +19,17 @@ function isGroupableToolMessage(message: ChatMessage): message is ChatMessage &
|
||||
return Boolean(message.isToolUse && message.toolName && !message.isSubagentContainer);
|
||||
}
|
||||
|
||||
export function groupConsecutiveTools(messages: ChatMessage[]): MessageListItem[] {
|
||||
// Messages that render nothing (e.g. reasoning hidden when showThinking is off)
|
||||
// shouldn't split an otherwise-continuous run of the same tool — providers like
|
||||
// Codex interleave hidden reasoning between consecutive tool calls.
|
||||
function rendersNothing(message: ChatMessage, showThinking: boolean): boolean {
|
||||
return Boolean(message.isThinking && !showThinking);
|
||||
}
|
||||
|
||||
export function groupConsecutiveTools(
|
||||
messages: ChatMessage[],
|
||||
showThinking: boolean = true,
|
||||
): MessageListItem[] {
|
||||
const items: MessageListItem[] = [];
|
||||
let index = 0;
|
||||
|
||||
@@ -35,13 +45,22 @@ export function groupConsecutiveTools(messages: ChatMessage[]): MessageListItem[
|
||||
const run: ChatMessage[] = [message];
|
||||
let nextIndex = index + 1;
|
||||
|
||||
while (
|
||||
nextIndex < messages.length &&
|
||||
isGroupableToolMessage(messages[nextIndex]) &&
|
||||
messages[nextIndex].toolName === message.toolName
|
||||
) {
|
||||
run.push(messages[nextIndex]);
|
||||
nextIndex += 1;
|
||||
while (nextIndex < messages.length) {
|
||||
const candidate = messages[nextIndex];
|
||||
|
||||
// Skip invisible interleaved messages so they don't break the run.
|
||||
if (rendersNothing(candidate, showThinking)) {
|
||||
nextIndex += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isGroupableToolMessage(candidate) && candidate.toolName === message.toolName) {
|
||||
run.push(candidate);
|
||||
nextIndex += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (run.length >= TOOL_GROUP_THRESHOLD) {
|
||||
|
||||
@@ -120,7 +120,10 @@ function ChatMessagesPane({
|
||||
const messageKeyMapRef = useRef<WeakMap<ChatMessage, string>>(new WeakMap());
|
||||
const allocatedKeysRef = useRef<Set<string>>(new Set());
|
||||
const generatedMessageKeyCounterRef = useRef(0);
|
||||
const groupedVisibleMessages = useMemo(() => groupConsecutiveTools(visibleMessages), [visibleMessages]);
|
||||
const groupedVisibleMessages = useMemo(
|
||||
() => groupConsecutiveTools(visibleMessages, Boolean(showThinking)),
|
||||
[visibleMessages, showThinking],
|
||||
);
|
||||
|
||||
// Keep keys stable across prepends so existing MessageComponent instances retain local state.
|
||||
const getMessageKey = useCallback((message: ChatMessage) => {
|
||||
|
||||
Reference in New Issue
Block a user