feat(cli): implement compact tool output (#20974)

This commit is contained in:
Jarrod Whelan
2026-03-30 16:43:29 -07:00
committed by GitHub
parent 3e95b8ec59
commit 1df5c98b33
45 changed files with 2670 additions and 386 deletions
+117 -22
View File
@@ -84,6 +84,7 @@ import type { UseHistoryManagerReturn } from './useHistoryManager.js';
import { useLogger } from './useLogger.js';
import { SHELL_COMMAND_NAME } from '../constants.js';
import { mapToDisplay as mapTrackedToolCallsToDisplay } from './toolMapping.js';
import { isCompactTool } from '../components/messages/ToolGroupMessage.js';
import {
useToolScheduler,
type TrackedToolCall,
@@ -303,9 +304,32 @@ export const useGeminiStream = (
(tc) => !pushedToolCallIdsRef.current.has(tc.request.callId),
);
if (toolsToPush.length > 0) {
const isCompactModeEnabled =
settings.merged.ui?.compactToolOutput === true;
const firstToolToPush = toolsToPush[0];
const tcIndex = toolCalls.indexOf(firstToolToPush);
const prevTool = tcIndex > 0 ? toolCalls[tcIndex - 1] : null;
let borderTop = isFirstToolInGroupRef.current;
if (!borderTop && prevTool) {
// If the first tool in this push is non-compact but follows a compact tool,
// we must start a new border group.
const currentIsCompact = isCompactTool(
mapTrackedToolCallsToDisplay(firstToolToPush).tools[0],
isCompactModeEnabled,
);
const prevWasCompact = isCompactTool(
mapTrackedToolCallsToDisplay(prevTool).tools[0],
isCompactModeEnabled,
);
if (!currentIsCompact && prevWasCompact) {
borderTop = true;
}
}
addItem(
mapTrackedToolCallsToDisplay(toolsToPush as TrackedToolCall[], {
borderTop: isFirstToolInGroupRef.current,
borderTop,
borderBottom: true,
borderColor: theme.border.default,
borderDimColor: false,
@@ -340,9 +364,7 @@ export const useGeminiStream = (
}
// Handle tool response submission immediately when tools complete
await handleCompletedTools(
completedToolCallsFromScheduler as TrackedToolCall[],
);
await handleCompletedTools(completedToolCallsFromScheduler);
}
},
config,
@@ -472,26 +494,85 @@ export const useGeminiStream = (
if (toolsToPush.length > 0) {
const newPushed = new Set(pushedToolCallIdsRef.current);
const isFirstInThisPush = isFirstToolInGroupRef.current;
const isCompactModeEnabled =
settings.merged.ui?.compactToolOutput === true;
const groups: TrackedToolCall[][] = [];
let currentGroup: TrackedToolCall[] = [];
for (const tc of toolsToPush) {
newPushed.add(tc.request.callId);
if (tc.tool?.kind === Kind.Agent) {
currentGroup.push(tc);
} else {
if (currentGroup.length > 0) {
groups.push(currentGroup);
currentGroup = [];
}
groups.push([tc]);
}
}
if (currentGroup.length > 0) {
groups.push(currentGroup);
}
const isLastInBatch =
toolsToPush[toolsToPush.length - 1] === toolCalls[toolCalls.length - 1];
for (let i = 0; i < groups.length; i++) {
const group = groups[i];
const isFirstInBatch = i === 0 && isFirstInThisPush;
const lastTcInGroup = group[group.length - 1];
const tcIndexInBatch = toolCalls.indexOf(lastTcInGroup);
const isLastInBatch = tcIndexInBatch === toolCalls.length - 1;
const historyItem = mapTrackedToolCallsToDisplay(toolsToPush, {
borderTop: isFirstToolInGroupRef.current,
borderBottom: isLastInBatch,
...getToolGroupBorderAppearance(
{ type: 'tool_group', tools: toolCalls },
activeShellPtyId,
!!isShellFocused,
[],
backgroundTasks,
),
});
addItem(historyItem);
const nextTcInBatch =
tcIndexInBatch < toolCalls.length - 1
? toolCalls[tcIndexInBatch + 1]
: null;
const prevTcInBatch =
toolCalls.indexOf(group[0]) > 0
? toolCalls[toolCalls.indexOf(group[0]) - 1]
: null;
const historyItem = mapTrackedToolCallsToDisplay(group, {
...getToolGroupBorderAppearance(
{ type: 'tool_group', tools: toolCalls },
activeShellPtyId,
!!isShellFocused,
[],
backgroundTasks,
),
});
// Determine if this group starts with a compact tool
const currentIsCompact =
historyItem.tools.length === 1 &&
isCompactTool(historyItem.tools[0], isCompactModeEnabled);
let nextIsCompact = false;
if (nextTcInBatch) {
const nextHistoryItem = mapTrackedToolCallsToDisplay(nextTcInBatch);
nextIsCompact =
nextHistoryItem.tools.length === 1 &&
isCompactTool(nextHistoryItem.tools[0], isCompactModeEnabled);
}
let prevWasCompact = false;
if (prevTcInBatch) {
const prevHistoryItem = mapTrackedToolCallsToDisplay(prevTcInBatch);
prevWasCompact =
prevHistoryItem.tools.length === 1 &&
isCompactTool(prevHistoryItem.tools[0], isCompactModeEnabled);
}
historyItem.borderTop =
isFirstInBatch || (!currentIsCompact && prevWasCompact);
historyItem.borderBottom = currentIsCompact
? isLastInBatch && !nextIsCompact
: isLastInBatch || nextIsCompact;
addItem(historyItem);
}
setPushedToolCallIds(newPushed);
@@ -516,6 +597,7 @@ export const useGeminiStream = (
activeShellPtyId,
isShellFocused,
backgroundTasks,
settings.merged.ui?.compactToolOutput,
]);
const pendingToolGroupItems = useMemo((): HistoryItemWithoutId[] => {
const remainingTools = toolCalls.filter(
@@ -569,8 +651,7 @@ export const useGeminiStream = (
toolCalls.length > 0 &&
toolCalls.every((tc) => pushedToolCallIds.has(tc.request.callId));
const anyVisibleInHistory = pushedToolCallIds.size > 0;
const anyVisibleInPending = remainingTools.some((tc) => {
const isToolVisible = (tc: TrackedToolCall) => {
const displayName = tc.tool?.displayName ?? tc.request.name;
let hasResultDisplay = false;
@@ -607,12 +688,25 @@ export const useGeminiStream = (
// ToolGroupMessage now shows all non-canceled tools, so they are visible
// in pending and we need to draw the closing border for them.
return true;
});
};
let lastVisibleIsCompact = false;
const isCompactModeEnabled = settings.merged.ui?.compactToolOutput === true;
for (let i = toolCalls.length - 1; i >= 0; i--) {
if (isToolVisible(toolCalls[i])) {
const mapped = mapTrackedToolCallsToDisplay(toolCalls[i]);
lastVisibleIsCompact = mapped.tools[0]
? isCompactTool(mapped.tools[0], isCompactModeEnabled)
: false;
break;
}
}
if (
toolCalls.length > 0 &&
!(allTerminal && allPushed) &&
(anyVisibleInHistory || anyVisibleInPending)
toolCalls.some(isToolVisible) &&
!lastVisibleIsCompact
) {
items.push({
type: 'tool_group' as const,
@@ -630,6 +724,7 @@ export const useGeminiStream = (
activeShellPtyId,
isShellFocused,
backgroundTasks,
settings.merged.ui?.compactToolOutput,
]);
const lastQueryRef = useRef<PartListUnion | null>(null);