mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-21 02:24:09 -07:00
feat(cli): implement compact tool output (#20974)
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user