diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts
index 300338f991..21c7f96a1b 100644
--- a/packages/cli/src/config/settingsSchema.ts
+++ b/packages/cli/src/config/settingsSchema.ts
@@ -380,7 +380,25 @@ const SETTINGS_SCHEMA = {
requiresRestart: false,
default: false,
description:
- 'Show model thinking summaries inline in the conversation.',
+ 'Show model thinking summaries inline in the conversation (deprecated; prefer the specific thinking modes).',
+ showInDialog: true,
+ },
+ showInlineThinkingFull: {
+ type: 'boolean',
+ label: 'Show Inline Thinking (Full)',
+ category: 'UI',
+ requiresRestart: false,
+ default: false,
+ description: 'Show full model thinking details inline.',
+ showInDialog: true,
+ },
+ showInlineThinkingSummary: {
+ type: 'boolean',
+ label: 'Show Inline Thinking (Summary)',
+ category: 'UI',
+ requiresRestart: false,
+ default: false,
+ description: 'Show a short summary of model thinking inline.',
showInDialog: true,
},
showStatusInTitle: {
diff --git a/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.tsx b/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.tsx
index fec35d46c3..abe68ebbf9 100644
--- a/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.tsx
+++ b/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.tsx
@@ -6,6 +6,7 @@
import { Box, Text } from 'ink';
import { useUIState } from '../contexts/UIStateContext.js';
+import { useSettings } from '../contexts/SettingsContext.js';
import { AppHeader } from './AppHeader.js';
import { HistoryItemDisplay } from './HistoryItemDisplay.js';
import { QuittingDisplay } from './QuittingDisplay.js';
@@ -15,15 +16,18 @@ import { useConfirmingTool } from '../hooks/useConfirmingTool.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { ToolStatusIndicator, ToolInfo } from './messages/ToolShared.js';
import { theme } from '../semantic-colors.js';
+import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';
export const AlternateBufferQuittingDisplay = () => {
const { version } = useAppContext();
const uiState = useUIState();
+ const settings = useSettings();
const config = useConfig();
const confirmingTool = useConfirmingTool();
const showPromptedTool =
config.isEventDrivenSchedulerEnabled() && confirmingTool !== null;
+ const inlineEnabled = getInlineThinkingMode(settings) !== 'off';
// We render the entire chat history and header here to ensure that the
// conversation history is visible to the user after the app quits and the
@@ -47,6 +51,7 @@ export const AlternateBufferQuittingDisplay = () => {
item={h}
isPending={false}
commands={uiState.slashCommands}
+ inlineEnabled={inlineEnabled}
/>
))}
{uiState.pendingHistoryItems.map((item, i) => (
@@ -59,6 +64,7 @@ export const AlternateBufferQuittingDisplay = () => {
isFocused={false}
activeShellPtyId={uiState.activePtyId}
embeddedShellFocused={uiState.embeddedShellFocused}
+ inlineEnabled={inlineEnabled}
/>
))}
{showPromptedTool && (
diff --git a/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx b/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
index d213b489cb..e2938be0b8 100644
--- a/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
+++ b/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
@@ -237,7 +237,7 @@ describe('', () => {
const item: HistoryItem = {
...baseItem,
type: 'thinking',
- thoughts: [{ subject: 'Thinking', description: 'test' }],
+ thought: { subject: 'Thinking', description: 'test' },
};
const { lastFrame } = renderWithProviders(
,
@@ -250,7 +250,7 @@ describe('', () => {
const item: HistoryItem = {
...baseItem,
type: 'thinking',
- thoughts: [{ subject: 'Thinking', description: 'test' }],
+ thought: { subject: 'Thinking', description: 'test' },
};
const { lastFrame } = renderWithProviders(
,
diff --git a/packages/cli/src/ui/components/HistoryItemDisplay.tsx b/packages/cli/src/ui/components/HistoryItemDisplay.tsx
index f83c2d34d9..8b13a004a4 100644
--- a/packages/cli/src/ui/components/HistoryItemDisplay.tsx
+++ b/packages/cli/src/ui/components/HistoryItemDisplay.tsx
@@ -68,7 +68,7 @@ export const HistoryItemDisplay: React.FC = ({
{/* Render standard message types */}
{itemForDisplay.type === 'thinking' && inlineEnabled && (
{
availableTerminalHeight,
} = uiState;
- const inlineEnabled = settings.merged.ui?.showInlineThinking;
+ const inlineEnabled = getInlineThinkingMode(settings) !== 'off';
const historyItems = useMemo(
() =>
diff --git a/packages/cli/src/ui/components/QuittingDisplay.test.tsx b/packages/cli/src/ui/components/QuittingDisplay.test.tsx
index 79cc7e5d7b..ab20a12d83 100644
--- a/packages/cli/src/ui/components/QuittingDisplay.test.tsx
+++ b/packages/cli/src/ui/components/QuittingDisplay.test.tsx
@@ -12,6 +12,17 @@ import { useUIState, type UIState } from '../contexts/UIStateContext.js';
import { useTerminalSize } from '../hooks/useTerminalSize.js';
vi.mock('../contexts/UIStateContext.js');
+vi.mock('../contexts/SettingsContext.js', () => ({
+ useSettings: () => ({
+ merged: {
+ ui: {
+ showInlineThinking: false,
+ showInlineThinkingFull: false,
+ showInlineThinkingSummary: false,
+ },
+ },
+ }),
+}));
vi.mock('../hooks/useTerminalSize.js');
vi.mock('./HistoryItemDisplay.js', async () => {
const { Text } = await vi.importActual('ink');
diff --git a/packages/cli/src/ui/components/QuittingDisplay.tsx b/packages/cli/src/ui/components/QuittingDisplay.tsx
index ee81f92012..f2770b998d 100644
--- a/packages/cli/src/ui/components/QuittingDisplay.tsx
+++ b/packages/cli/src/ui/components/QuittingDisplay.tsx
@@ -6,14 +6,18 @@
import { Box } from 'ink';
import { useUIState } from '../contexts/UIStateContext.js';
+import { useSettings } from '../contexts/SettingsContext.js';
import { HistoryItemDisplay } from './HistoryItemDisplay.js';
import { useTerminalSize } from '../hooks/useTerminalSize.js';
+import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';
export const QuittingDisplay = () => {
const uiState = useUIState();
+ const settings = useSettings();
const { rows: terminalHeight, columns: terminalWidth } = useTerminalSize();
const availableTerminalHeight = terminalHeight;
+ const inlineEnabled = getInlineThinkingMode(settings) !== 'off';
if (!uiState.quittingMessages) {
return null;
@@ -30,6 +34,7 @@ export const QuittingDisplay = () => {
terminalWidth={terminalWidth}
item={item}
isPending={false}
+ inlineEnabled={inlineEnabled}
/>
))}
diff --git a/packages/cli/src/ui/components/messages/ThinkingMessage.test.tsx b/packages/cli/src/ui/components/messages/ThinkingMessage.test.tsx
index 582776bb82..59c62b3cb0 100644
--- a/packages/cli/src/ui/components/messages/ThinkingMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ThinkingMessage.test.tsx
@@ -9,38 +9,35 @@ import { render } from '../../../test-utils/render.js';
import { ThinkingMessage } from './ThinkingMessage.js';
describe('ThinkingMessage', () => {
- it('renders thinking header with count', () => {
+ it('renders thinking header', () => {
const { lastFrame } = render(
,
);
expect(lastFrame()).toContain('Thinking');
- expect(lastFrame()).toContain('(2)');
});
- it('renders with single thought', () => {
+ it('renders with thought subject', () => {
const { lastFrame } = render(
,
);
- expect(lastFrame()).toContain('(1)');
+ expect(lastFrame()).toContain('Processing');
});
it('renders thought content', () => {
const { lastFrame } = render(
,
);
@@ -51,9 +48,12 @@ describe('ThinkingMessage', () => {
it('renders empty state gracefully', () => {
const { lastFrame } = render(
- ,
+ ,
);
- expect(lastFrame()).toContain('(0)');
+ expect(lastFrame()).toContain('Thinking');
});
});
diff --git a/packages/cli/src/ui/components/messages/ThinkingMessage.tsx b/packages/cli/src/ui/components/messages/ThinkingMessage.tsx
index e1453add44..3c2426d918 100644
--- a/packages/cli/src/ui/components/messages/ThinkingMessage.tsx
+++ b/packages/cli/src/ui/components/messages/ThinkingMessage.tsx
@@ -10,16 +10,18 @@ import type { ThoughtSummary } from '@google/gemini-cli-core';
import { MaxSizedBox, MINIMUM_MAX_HEIGHT } from '../shared/MaxSizedBox.js';
interface ThinkingMessageProps {
- thoughts: ThoughtSummary[];
+ thought: ThoughtSummary;
terminalWidth: number;
availableTerminalHeight?: number;
}
export const ThinkingMessage: React.FC = ({
- thoughts,
+ thought,
terminalWidth,
availableTerminalHeight,
}) => {
+ const subject = thought.subject.trim();
+ const description = thought.description.trim();
const contentMaxHeight =
availableTerminalHeight !== undefined
? Math.max(availableTerminalHeight - 4, MINIMUM_MAX_HEIGHT)
@@ -39,23 +41,22 @@ export const ThinkingMessage: React.FC = ({
Thinking
- ({thoughts.length})
- {thoughts.map((thought, index) => (
-
- {thought.subject && (
+ {(subject || description) && (
+
+ {subject && (
- {thought.subject}
+ {subject}
)}
- {thought.description || ' '}
+ {description && {description}}
- ))}
+ )}
);
diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts
index 66ffa35799..aaa8d9c440 100644
--- a/packages/cli/src/ui/hooks/useGeminiStream.ts
+++ b/packages/cli/src/ui/hooks/useGeminiStream.ts
@@ -63,6 +63,7 @@ import { isAtCommand, isSlashCommand } from '../utils/commandUtils.js';
import { useShellCommandProcessor } from './shellCommandProcessor.js';
import { handleAtCommand } from './atCommandProcessor.js';
import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js';
+import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';
import { useStateAndRef } from './useStateAndRef.js';
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
import { useLogger } from './useLogger.js';
@@ -78,6 +79,29 @@ import {
} from './useToolScheduler.js';
import { promises as fs } from 'node:fs';
import path from 'node:path';
+
+const MAX_THOUGHT_SUMMARY_LENGTH = 140;
+
+function summarizeThought(thought: ThoughtSummary): ThoughtSummary {
+ const subject = thought.subject.trim();
+ if (subject) {
+ return { subject, description: '' };
+ }
+
+ const description = thought.description.trim();
+ if (!description) {
+ return { subject: '', description: '' };
+ }
+
+ if (description.length <= MAX_THOUGHT_SUMMARY_LENGTH) {
+ return { subject: description, description: '' };
+ }
+
+ const trimmed = description
+ .slice(0, MAX_THOUGHT_SUMMARY_LENGTH - 3)
+ .trimEnd();
+ return { subject: `${trimmed}...`, description: '' };
+}
import { useSessionStats } from '../contexts/SessionContext.js';
import { useKeypress } from './useKeypress.js';
import type { LoadedSettings } from '../../config/settings.js';
@@ -762,7 +786,7 @@ export const useGeminiStream = (
pendingHistoryItemRef.current?.type !== 'gemini' &&
pendingHistoryItemRef.current?.type !== 'gemini_content'
) {
- // Flush any pending item (including thinking items) before starting gemini content
+ // Flush any pending item before starting gemini content
if (pendingHistoryItemRef.current) {
addItem(pendingHistoryItemRef.current, userMessageTimestamp);
}
@@ -810,34 +834,25 @@ export const useGeminiStream = (
(eventValue: ThoughtSummary, userMessageTimestamp: number) => {
setThought(eventValue);
- // Only accumulate thoughts in history if inline thinking is enabled
- if (!settings.merged.ui?.showInlineThinking) {
+ const inlineThinkingMode = getInlineThinkingMode(settings);
+ if (inlineThinkingMode === 'off') {
return;
}
- if (pendingHistoryItemRef.current?.type === 'thinking') {
- // Accumulate thoughts in the existing thinking item
- setPendingHistoryItem((prev) => ({
+ const thoughtForDisplay =
+ inlineThinkingMode === 'summary'
+ ? summarizeThought(eventValue)
+ : eventValue;
+
+ addItem(
+ {
type: 'thinking',
- thoughts: [...(prev as HistoryItemThinking).thoughts, eventValue],
- }));
- } else {
- // Flush any existing pending item and start a new thinking item
- if (pendingHistoryItemRef.current) {
- addItem(pendingHistoryItemRef.current, userMessageTimestamp);
- }
- setPendingHistoryItem({
- type: 'thinking',
- thoughts: [eventValue],
- } as HistoryItemThinking);
- }
+ thought: thoughtForDisplay,
+ } as HistoryItemThinking,
+ userMessageTimestamp,
+ );
},
- [
- addItem,
- pendingHistoryItemRef,
- setPendingHistoryItem,
- settings.merged.ui?.showInlineThinking,
- ],
+ [addItem, settings],
);
const handleUserCancelledEvent = useCallback(
@@ -1279,10 +1294,6 @@ export const useGeminiStream = (
}
startNewPrompt();
setThought(null); // Reset thought when starting a new prompt
- // Clear any pending thinking item from previous prompt
- if (pendingHistoryItemRef.current?.type === 'thinking') {
- setPendingHistoryItem(null);
- }
}
setIsResponding(true);
diff --git a/packages/cli/src/ui/types.ts b/packages/cli/src/ui/types.ts
index df9600705a..9f99af1f0c 100644
--- a/packages/cli/src/ui/types.ts
+++ b/packages/cli/src/ui/types.ts
@@ -212,7 +212,7 @@ export interface ChatDetail {
export type HistoryItemThinking = HistoryItemBase & {
type: 'thinking';
- thoughts: ThoughtSummary[];
+ thought: ThoughtSummary;
};
export type HistoryItemChatList = HistoryItemBase & {