summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJAYADITYA <[email protected]>2025-08-12 09:43:57 +0530
committerGitHub <[email protected]>2025-08-12 04:13:57 +0000
commit2d1a6af890da1e9437cd1a1774e2c7fc7ad32957 (patch)
tree841a9d59013793e2f6485d8e2fe87a2e7328373b
parentf9efb2e24f41d8738d6ea8d1b8e8be2dff3bb83b (diff)
feat(cli): support single Ctrl+C to cancel streaming, preserving double Ctrl+C to exit (#5838)
-rw-r--r--packages/cli/src/ui/App.tsx5
-rw-r--r--packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx4
-rw-r--r--packages/cli/src/ui/hooks/useGeminiStream.ts52
3 files changed, 40 insertions, 21 deletions
diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx
index c4f67294..e952d6b2 100644
--- a/packages/cli/src/ui/App.tsx
+++ b/packages/cli/src/ui/App.tsx
@@ -545,6 +545,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
initError,
pendingHistoryItems: pendingGeminiHistoryItems,
thought,
+ cancelOngoingRequest,
} = useGeminiStream(
config.getGeminiClient(),
history,
@@ -655,6 +656,9 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
if (isAuthenticating) {
return;
}
+ if (!ctrlCPressedOnce) {
+ cancelOngoingRequest?.();
+ }
handleExit(ctrlCPressedOnce, setCtrlCPressedOnce, ctrlCTimerRef);
} else if (keyMatchers[Command.EXIT](key)) {
if (buffer.text.length > 0) {
@@ -686,6 +690,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
ctrlDTimerRef,
handleSlashCommand,
isAuthenticating,
+ cancelOngoingRequest,
],
);
diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
index fcdb743f..88b25b86 100644
--- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
+++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
@@ -56,9 +56,9 @@ export const ToolConfirmationMessage: React.FC<
onConfirm(outcome);
};
- useInput((_, key) => {
+ useInput((input, key) => {
if (!isFocused) return;
- if (key.escape) {
+ if (key.escape || (key.ctrl && (input === 'c' || input === 'C'))) {
handleConfirm(ToolConfirmationOutcome.Cancel);
}
});
diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts
index 58bec431..6385d267 100644
--- a/packages/cli/src/ui/hooks/useGeminiStream.ts
+++ b/packages/cli/src/ui/hooks/useGeminiStream.ts
@@ -183,26 +183,39 @@ export const useGeminiStream = (
return StreamingState.Idle;
}, [isResponding, toolCalls]);
+ const cancelOngoingRequest = useCallback(() => {
+ if (streamingState !== StreamingState.Responding) {
+ return;
+ }
+ if (turnCancelledRef.current) {
+ return;
+ }
+ turnCancelledRef.current = true;
+ abortControllerRef.current?.abort();
+ if (pendingHistoryItemRef.current) {
+ addItem(pendingHistoryItemRef.current, Date.now());
+ }
+ addItem(
+ {
+ type: MessageType.INFO,
+ text: 'Request cancelled.',
+ },
+ Date.now(),
+ );
+ setPendingHistoryItem(null);
+ onCancelSubmit();
+ setIsResponding(false);
+ }, [
+ streamingState,
+ addItem,
+ setPendingHistoryItem,
+ onCancelSubmit,
+ pendingHistoryItemRef,
+ ]);
+
useInput((_input, key) => {
- if (streamingState === StreamingState.Responding && key.escape) {
- if (turnCancelledRef.current) {
- return;
- }
- turnCancelledRef.current = true;
- abortControllerRef.current?.abort();
- if (pendingHistoryItemRef.current) {
- addItem(pendingHistoryItemRef.current, Date.now());
- }
- addItem(
- {
- type: MessageType.INFO,
- text: 'Request cancelled.',
- },
- Date.now(),
- );
- setPendingHistoryItem(null);
- onCancelSubmit();
- setIsResponding(false);
+ if (key.escape) {
+ cancelOngoingRequest();
}
});
@@ -954,5 +967,6 @@ export const useGeminiStream = (
initError,
pendingHistoryItems,
thought,
+ cancelOngoingRequest,
};
};