2025-04-18 17:44:24 -07:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
2025-05-22 05:57:53 +00:00
import { useState , useRef , useCallback , useEffect , useMemo } from 'react' ;
Initial commit of Gemini Code CLI
This commit introduces the initial codebase for the Gemini Code CLI, a command-line interface designed to facilitate interaction with the Gemini API for software engineering tasks.
The code was migrated from a previous git repository as a single squashed commit.
Core Features & Components:
* **Gemini Integration:** Leverages the `@google/genai` SDK to interact with the Gemini models, supporting chat history, streaming responses, and function calling (tools).
* **Terminal UI:** Built with Ink (React for CLIs) providing an interactive chat interface within the terminal, including input prompts, message display, loading indicators, and tool interaction elements.
* **Tooling Framework:** Implements a robust tool system allowing Gemini to interact with the local environment. Includes tools for:
* File system listing (`ls`)
* File reading (`read-file`)
* Content searching (`grep`)
* File globbing (`glob`)
* File editing (`edit`)
* File writing (`write-file`)
* Executing bash commands (`terminal`)
* **State Management:** Handles the streaming state of Gemini responses and manages the conversation history.
* **Configuration:** Parses command-line arguments (`yargs`) and loads environment variables (`dotenv`) for setup.
* **Project Structure:** Organized into `core`, `ui`, `tools`, `config`, and `utils` directories using TypeScript. Includes basic build (`tsc`) and start scripts.
This initial version establishes the foundation for a powerful CLI tool enabling developers to use Gemini for coding assistance directly in their terminal environment.
---
Created by yours truly: __Gemini Code__
2025-04-15 21:41:08 -07:00
import { useInput } from 'ink' ;
2025-04-18 18:08:43 -04:00
import {
2025-04-19 19:45:42 +01:00
GeminiClient ,
2025-05-06 16:20:28 -07:00
GeminiEventType as ServerGeminiEventType ,
2025-05-14 22:14:15 +00:00
ServerGeminiStreamEvent as GeminiEvent ,
ServerGeminiContentEvent as ContentEvent ,
ServerGeminiErrorEvent as ErrorEvent ,
2025-04-19 19:45:42 +01:00
getErrorMessage ,
isNodeError ,
2025-04-20 21:06:22 +01:00
Config ,
2025-05-21 07:36:22 +00:00
MessageSenderType ,
2025-05-22 05:57:53 +00:00
ToolCallRequestInfo ,
2025-06-05 16:04:25 -04:00
logUserPrompt ,
2025-06-07 14:27:22 -07:00
} from '@gemini-cli/core' ;
2025-06-01 14:16:24 -07:00
import { type PartListUnion } from '@google/genai' ;
2025-04-19 19:45:42 +01:00
import {
2025-04-21 11:49:46 -07:00
StreamingState ,
2025-05-07 12:57:19 -07:00
HistoryItemWithoutId ,
2025-05-14 22:14:15 +00:00
HistoryItemToolGroup ,
2025-05-14 12:37:17 -07:00
MessageType ,
2025-06-01 14:16:24 -07:00
ToolCallStatus ,
2025-04-19 19:45:42 +01:00
} from '../types.js' ;
2025-05-02 14:39:39 -07:00
import { isAtCommand } from '../utils/commandUtils.js' ;
2025-06-07 22:04:57 -07:00
import { parseAndFormatApiError } from '../utils/errorParsing.js' ;
2025-04-30 00:26:07 +00:00
import { useShellCommandProcessor } from './shellCommandProcessor.js' ;
2025-05-02 14:39:39 -07:00
import { handleAtCommand } from './atCommandProcessor.js' ;
2025-05-07 21:15:41 -07:00
import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js' ;
2025-05-07 12:57:19 -07:00
import { useStateAndRef } from './useStateAndRef.js' ;
2025-05-06 16:20:28 -07:00
import { UseHistoryManagerReturn } from './useHistoryManager.js' ;
2025-05-21 07:36:22 +00:00
import { useLogger } from './useLogger.js' ;
2025-06-01 14:16:24 -07:00
import {
useReactToolScheduler ,
mapToDisplay as mapTrackedToolCallsToDisplay ,
TrackedToolCall ,
TrackedCompletedToolCall ,
TrackedCancelledToolCall ,
} from './useReactToolScheduler.js' ;
2025-04-28 12:38:07 -07:00
2025-05-29 22:30:18 +00:00
export function mergePartListUnions ( list : PartListUnion [ ] ) : PartListUnion {
const resultParts : PartListUnion = [ ] ;
for ( const item of list ) {
if ( Array . isArray ( item ) ) {
resultParts . push ( . . . item ) ;
} else {
resultParts . push ( item ) ;
}
}
return resultParts ;
}
2025-05-14 22:14:15 +00:00
enum StreamProcessingStatus {
Completed ,
UserCancelled ,
Error ,
}
/ * *
2025-06-01 14:16:24 -07:00
* Manages the Gemini stream , including user input , command processing ,
* API interaction , and tool call lifecycle .
2025-05-14 22:14:15 +00:00
* /
Initial commit of Gemini Code CLI
This commit introduces the initial codebase for the Gemini Code CLI, a command-line interface designed to facilitate interaction with the Gemini API for software engineering tasks.
The code was migrated from a previous git repository as a single squashed commit.
Core Features & Components:
* **Gemini Integration:** Leverages the `@google/genai` SDK to interact with the Gemini models, supporting chat history, streaming responses, and function calling (tools).
* **Terminal UI:** Built with Ink (React for CLIs) providing an interactive chat interface within the terminal, including input prompts, message display, loading indicators, and tool interaction elements.
* **Tooling Framework:** Implements a robust tool system allowing Gemini to interact with the local environment. Includes tools for:
* File system listing (`ls`)
* File reading (`read-file`)
* Content searching (`grep`)
* File globbing (`glob`)
* File editing (`edit`)
* File writing (`write-file`)
* Executing bash commands (`terminal`)
* **State Management:** Handles the streaming state of Gemini responses and manages the conversation history.
* **Configuration:** Parses command-line arguments (`yargs`) and loads environment variables (`dotenv`) for setup.
* **Project Structure:** Organized into `core`, `ui`, `tools`, `config`, and `utils` directories using TypeScript. Includes basic build (`tsc`) and start scripts.
This initial version establishes the foundation for a powerful CLI tool enabling developers to use Gemini for coding assistance directly in their terminal environment.
---
Created by yours truly: __Gemini Code__
2025-04-15 21:41:08 -07:00
export const useGeminiStream = (
2025-06-05 21:33:24 +00:00
geminiClient : GeminiClient | null ,
2025-05-06 16:20:28 -07:00
addItem : UseHistoryManagerReturn [ 'addItem' ] ,
2025-05-05 20:48:34 +00:00
setShowHelp : React.Dispatch < React.SetStateAction < boolean > > ,
2025-04-20 21:06:22 +01:00
config : Config ,
2025-05-13 23:55:49 +00:00
onDebugMessage : ( message : string ) = > void ,
2025-05-23 08:47:19 -07:00
handleSlashCommand : (
cmd : PartListUnion ,
) = > import ( './slashCommandProcessor.js' ) . SlashCommandActionReturn | boolean ,
2025-05-19 16:11:45 -07:00
shellModeActive : boolean ,
Initial commit of Gemini Code CLI
This commit introduces the initial codebase for the Gemini Code CLI, a command-line interface designed to facilitate interaction with the Gemini API for software engineering tasks.
The code was migrated from a previous git repository as a single squashed commit.
Core Features & Components:
* **Gemini Integration:** Leverages the `@google/genai` SDK to interact with the Gemini models, supporting chat history, streaming responses, and function calling (tools).
* **Terminal UI:** Built with Ink (React for CLIs) providing an interactive chat interface within the terminal, including input prompts, message display, loading indicators, and tool interaction elements.
* **Tooling Framework:** Implements a robust tool system allowing Gemini to interact with the local environment. Includes tools for:
* File system listing (`ls`)
* File reading (`read-file`)
* Content searching (`grep`)
* File globbing (`glob`)
* File editing (`edit`)
* File writing (`write-file`)
* Executing bash commands (`terminal`)
* **State Management:** Handles the streaming state of Gemini responses and manages the conversation history.
* **Configuration:** Parses command-line arguments (`yargs`) and loads environment variables (`dotenv`) for setup.
* **Project Structure:** Organized into `core`, `ui`, `tools`, `config`, and `utils` directories using TypeScript. Includes basic build (`tsc`) and start scripts.
This initial version establishes the foundation for a powerful CLI tool enabling developers to use Gemini for coding assistance directly in their terminal environment.
---
Created by yours truly: __Gemini Code__
2025-04-15 21:41:08 -07:00
) = > {
2025-04-17 18:06:21 -04:00
const [ initError , setInitError ] = useState < string | null > ( null ) ;
const abortControllerRef = useRef < AbortController | null > ( null ) ;
2025-05-16 16:45:58 +00:00
const [ isResponding , setIsResponding ] = useState < boolean > ( false ) ;
2025-05-07 12:57:19 -07:00
const [ pendingHistoryItemRef , setPendingHistoryItem ] =
useStateAndRef < HistoryItemWithoutId | null > ( null ) ;
2025-05-21 07:36:22 +00:00
const logger = useLogger ( ) ;
2025-06-01 14:16:24 -07:00
const [
toolCalls ,
scheduleToolCalls ,
cancelAllToolCalls ,
markToolsAsSubmitted ,
] = useReactToolScheduler (
( completedToolCallsFromScheduler ) = > {
// This onComplete is called when ALL scheduled tools for a given batch are done.
if ( completedToolCallsFromScheduler . length > 0 ) {
// Add the final state of these tools to the history for display.
// The new useEffect will handle submitting their responses.
addItem (
mapTrackedToolCallsToDisplay (
completedToolCallsFromScheduler as TrackedToolCall [ ] ,
) ,
Date . now ( ) ,
) ;
2025-05-27 15:40:18 -07:00
}
} ,
config ,
setPendingHistoryItem ,
) ;
2025-06-01 14:16:24 -07:00
const pendingToolCallGroupDisplay = useMemo (
( ) = >
toolCalls . length ? mapTrackedToolCallsToDisplay ( toolCalls ) : undefined ,
2025-05-22 05:57:53 +00:00
[ toolCalls ] ,
) ;
2025-04-17 18:06:21 -04:00
2025-05-16 16:45:58 +00:00
const onExec = useCallback ( async ( done : Promise < void > ) = > {
setIsResponding ( true ) ;
await done ;
setIsResponding ( false ) ;
} , [ ] ) ;
2025-04-30 00:26:07 +00:00
const { handleShellCommand } = useShellCommandProcessor (
2025-05-06 16:20:28 -07:00
addItem ,
2025-05-21 13:16:50 -07:00
setPendingHistoryItem ,
2025-05-16 16:45:58 +00:00
onExec ,
2025-05-13 23:55:49 +00:00
onDebugMessage ,
2025-04-30 00:26:07 +00:00
config ,
) ;
2025-05-24 00:44:17 -07:00
const streamingState = useMemo ( ( ) = > {
2025-06-01 14:16:24 -07:00
if ( toolCalls . some ( ( tc ) = > tc . status === 'awaiting_approval' ) ) {
2025-05-24 00:44:17 -07:00
return StreamingState . WaitingForConfirmation ;
}
if (
isResponding ||
toolCalls . some (
2025-06-01 14:16:24 -07:00
( tc ) = >
tc . status === 'executing' ||
tc . status === 'scheduled' ||
tc . status === 'validating' ,
2025-05-24 00:44:17 -07:00
)
) {
return StreamingState . Responding ;
}
return StreamingState . Idle ;
} , [ isResponding , toolCalls ] ) ;
2025-05-06 16:20:28 -07:00
useInput ( ( _input , key ) = > {
2025-05-09 23:29:02 -07:00
if ( streamingState !== StreamingState . Idle && key . escape ) {
2025-04-17 18:06:21 -04:00
abortControllerRef . current ? . abort ( ) ;
2025-06-01 14:16:24 -07:00
cancelAllToolCalls ( ) ; // Also cancel any pending/executing tool calls
2025-04-17 18:06:21 -04:00
}
} ) ;
2025-05-24 00:44:17 -07:00
const prepareQueryForGemini = useCallback (
async (
query : PartListUnion ,
userMessageTimestamp : number ,
2025-05-30 01:35:03 -07:00
abortSignal : AbortSignal ,
2025-05-24 00:44:17 -07:00
) : Promise < {
queryToSend : PartListUnion | null ;
shouldProceed : boolean ;
} > = > {
if ( typeof query === 'string' && query . trim ( ) . length === 0 ) {
return { queryToSend : null , shouldProceed : false } ;
}
2025-04-17 18:06:21 -04:00
2025-05-24 00:44:17 -07:00
let localQueryToSendToGemini : PartListUnion | null = null ;
2025-04-29 13:29:57 -07:00
2025-05-24 00:44:17 -07:00
if ( typeof query === 'string' ) {
const trimmedQuery = query . trim ( ) ;
2025-06-05 16:04:25 -04:00
logUserPrompt ( config , {
prompt : trimmedQuery ,
prompt_char_count : trimmedQuery.length ,
} ) ;
2025-05-24 00:44:17 -07:00
onDebugMessage ( ` User query: ' ${ trimmedQuery } ' ` ) ;
await logger ? . logMessage ( MessageSenderType . USER , trimmedQuery ) ;
2025-05-05 20:48:34 +00:00
2025-05-24 00:44:17 -07:00
// Handle UI-only commands first
const slashCommandResult = handleSlashCommand ( trimmedQuery ) ;
if ( typeof slashCommandResult === 'boolean' && slashCommandResult ) {
// Command was handled, and it doesn't require a tool call from here
return { queryToSend : null , shouldProceed : false } ;
} else if (
typeof slashCommandResult === 'object' &&
slashCommandResult . shouldScheduleTool
) {
// Slash command wants to schedule a tool call (e.g., /memory add)
const { toolName , toolArgs } = slashCommandResult ;
if ( toolName && toolArgs ) {
const toolCallRequest : ToolCallRequestInfo = {
callId : ` ${ toolName } - ${ Date . now ( ) } - ${ Math . random ( ) . toString ( 16 ) . slice ( 2 ) } ` ,
name : toolName ,
args : toolArgs ,
} ;
2025-06-01 14:16:24 -07:00
scheduleToolCalls ( [ toolCallRequest ] ) ;
2025-05-24 00:44:17 -07:00
}
return { queryToSend : null , shouldProceed : false } ; // Handled by scheduling the tool
2025-05-23 08:47:19 -07:00
}
2025-05-30 01:35:03 -07:00
if ( shellModeActive && handleShellCommand ( trimmedQuery , abortSignal ) ) {
2025-05-14 22:14:15 +00:00
return { queryToSend : null , shouldProceed : false } ;
2025-04-29 15:39:36 -07:00
}
2025-05-24 00:44:17 -07:00
// Handle @-commands (which might involve tool calls)
if ( isAtCommand ( trimmedQuery ) ) {
const atCommandResult = await handleAtCommand ( {
query : trimmedQuery ,
config ,
addItem ,
onDebugMessage ,
messageId : userMessageTimestamp ,
2025-05-30 01:35:03 -07:00
signal : abortSignal ,
2025-05-24 00:44:17 -07:00
} ) ;
if ( ! atCommandResult . shouldProceed ) {
return { queryToSend : null , shouldProceed : false } ;
}
localQueryToSendToGemini = atCommandResult . processedQuery ;
} else {
// Normal query for Gemini
addItem (
{ type : MessageType . USER , text : trimmedQuery } ,
userMessageTimestamp ,
) ;
localQueryToSendToGemini = trimmedQuery ;
}
2025-04-29 13:29:57 -07:00
} else {
2025-05-24 00:44:17 -07:00
// It's a function response (PartListUnion that isn't a string)
localQueryToSendToGemini = query ;
2025-04-20 20:20:40 +01:00
}
2025-05-14 22:14:15 +00:00
2025-05-24 00:44:17 -07:00
if ( localQueryToSendToGemini === null ) {
onDebugMessage (
'Query processing resulted in null, not sending to Gemini.' ,
) ;
return { queryToSend : null , shouldProceed : false } ;
}
return { queryToSend : localQueryToSendToGemini , shouldProceed : true } ;
} ,
[
config ,
addItem ,
onDebugMessage ,
handleShellCommand ,
handleSlashCommand ,
logger ,
shellModeActive ,
2025-06-01 14:16:24 -07:00
scheduleToolCalls ,
2025-05-24 00:44:17 -07:00
] ,
) ;
2025-04-20 20:20:40 +01:00
2025-05-14 22:14:15 +00:00
// --- Stream Event Handlers ---
2025-05-24 00:44:17 -07:00
const handleContentEvent = useCallback (
(
eventValue : ContentEvent [ 'value' ] ,
currentGeminiMessageBuffer : string ,
userMessageTimestamp : number ,
) : string = > {
let newGeminiMessageBuffer = currentGeminiMessageBuffer + eventValue ;
if (
pendingHistoryItemRef . current ? . type !== 'gemini' &&
pendingHistoryItemRef . current ? . type !== 'gemini_content'
) {
if ( pendingHistoryItemRef . current ) {
addItem ( pendingHistoryItemRef . current , userMessageTimestamp ) ;
}
setPendingHistoryItem ( { type : 'gemini' , text : '' } ) ;
newGeminiMessageBuffer = eventValue ;
}
// Split large messages for better rendering performance. Ideally,
// we should maximize the amount of output sent to <Static />.
const splitPoint = findLastSafeSplitPoint ( newGeminiMessageBuffer ) ;
if ( splitPoint === newGeminiMessageBuffer . length ) {
// Update the existing message with accumulated content
setPendingHistoryItem ( ( item ) = > ( {
type : item ? . type as 'gemini' | 'gemini_content' ,
text : newGeminiMessageBuffer ,
} ) ) ;
} else {
// This indicates that we need to split up this Gemini Message.
// Splitting a message is primarily a performance consideration. There is a
// <Static> component at the root of App.tsx which takes care of rendering
// content statically or dynamically. Everything but the last message is
// treated as static in order to prevent re-rendering an entire message history
// multiple times per-second (as streaming occurs). Prior to this change you'd
// see heavy flickering of the terminal. This ensures that larger messages get
// broken up so that there are more "statically" rendered.
const beforeText = newGeminiMessageBuffer . substring ( 0 , splitPoint ) ;
const afterText = newGeminiMessageBuffer . substring ( splitPoint ) ;
addItem (
{
type : pendingHistoryItemRef . current ? . type as
| 'gemini'
| 'gemini_content' ,
text : beforeText ,
} ,
userMessageTimestamp ,
) ;
setPendingHistoryItem ( { type : 'gemini_content' , text : afterText } ) ;
newGeminiMessageBuffer = afterText ;
}
return newGeminiMessageBuffer ;
} ,
[ addItem , pendingHistoryItemRef , setPendingHistoryItem ] ,
) ;
const handleUserCancelledEvent = useCallback (
( userMessageTimestamp : number ) = > {
2025-05-14 22:14:15 +00:00
if ( pendingHistoryItemRef . current ) {
2025-05-24 00:44:17 -07:00
if ( pendingHistoryItemRef . current . type === 'tool_group' ) {
const updatedTools = pendingHistoryItemRef . current . tools . map (
( tool ) = >
tool . status === ToolCallStatus . Pending ||
tool . status === ToolCallStatus . Confirming ||
tool . status === ToolCallStatus . Executing
? { . . . tool , status : ToolCallStatus.Canceled }
: tool ,
) ;
const pendingItem : HistoryItemToolGroup = {
. . . pendingHistoryItemRef . current ,
tools : updatedTools ,
} ;
addItem ( pendingItem , userMessageTimestamp ) ;
} else {
addItem ( pendingHistoryItemRef . current , userMessageTimestamp ) ;
}
setPendingHistoryItem ( null ) ;
2025-05-14 22:14:15 +00:00
}
addItem (
2025-05-24 00:44:17 -07:00
{ type : MessageType . INFO , text : 'User cancelled the request.' } ,
2025-05-14 22:14:15 +00:00
userMessageTimestamp ,
) ;
2025-05-24 00:44:17 -07:00
setIsResponding ( false ) ;
2025-06-01 14:16:24 -07:00
cancelAllToolCalls ( ) ;
2025-05-24 00:44:17 -07:00
} ,
2025-06-01 14:16:24 -07:00
[ addItem , pendingHistoryItemRef , setPendingHistoryItem , cancelAllToolCalls ] ,
2025-05-24 00:44:17 -07:00
) ;
2025-05-14 22:14:15 +00:00
2025-05-24 00:44:17 -07:00
const handleErrorEvent = useCallback (
( eventValue : ErrorEvent [ 'value' ] , userMessageTimestamp : number ) = > {
if ( pendingHistoryItemRef . current ) {
2025-05-14 22:14:15 +00:00
addItem ( pendingHistoryItemRef . current , userMessageTimestamp ) ;
2025-05-24 00:44:17 -07:00
setPendingHistoryItem ( null ) ;
2025-05-14 22:14:15 +00:00
}
2025-05-24 00:44:17 -07:00
addItem (
{ type : MessageType . ERROR , text : ` [API Error: ${ eventValue . message } ] ` } ,
userMessageTimestamp ,
) ;
} ,
[ addItem , pendingHistoryItemRef , setPendingHistoryItem ] ,
) ;
2025-05-14 22:14:15 +00:00
2025-06-03 19:19:49 +00:00
const handleChatCompressionEvent = useCallback (
( ) = >
addItem (
{
type : 'info' ,
text : ` IMPORTANT: this conversation approached the input token limit for ${ config . getModel ( ) } . We'll send a compressed context to the model for any future messages. ` ,
} ,
Date . now ( ) ,
) ,
[ addItem , config ] ,
) ;
2025-05-24 00:44:17 -07:00
const processGeminiStreamEvents = useCallback (
async (
stream : AsyncIterable < GeminiEvent > ,
userMessageTimestamp : number ,
) : Promise < StreamProcessingStatus > = > {
let geminiMessageBuffer = '' ;
const toolCallRequests : ToolCallRequestInfo [ ] = [ ] ;
for await ( const event of stream ) {
2025-06-03 19:19:49 +00:00
switch ( event . type ) {
case ServerGeminiEventType . Content :
geminiMessageBuffer = handleContentEvent (
event . value ,
geminiMessageBuffer ,
userMessageTimestamp ,
) ;
break ;
case ServerGeminiEventType . ToolCallRequest :
toolCallRequests . push ( event . value ) ;
break ;
case ServerGeminiEventType . UserCancelled :
handleUserCancelledEvent ( userMessageTimestamp ) ;
break ;
case ServerGeminiEventType . Error :
handleErrorEvent ( event . value , userMessageTimestamp ) ;
break ;
case ServerGeminiEventType . ChatCompressed :
handleChatCompressionEvent ( ) ;
break ;
case ServerGeminiEventType . ToolCallConfirmation :
case ServerGeminiEventType . ToolCallResponse :
// do nothing
break ;
default : {
// enforces exhaustive switch-case
const unreachable : never = event ;
return unreachable ;
}
2025-05-24 00:44:17 -07:00
}
2025-05-14 22:14:15 +00:00
}
2025-06-01 14:16:24 -07:00
if ( toolCallRequests . length > 0 ) {
scheduleToolCalls ( toolCallRequests ) ;
}
2025-05-24 00:44:17 -07:00
return StreamProcessingStatus . Completed ;
} ,
[
handleContentEvent ,
handleUserCancelledEvent ,
handleErrorEvent ,
2025-06-01 14:16:24 -07:00
scheduleToolCalls ,
2025-06-03 19:19:49 +00:00
handleChatCompressionEvent ,
2025-05-24 00:44:17 -07:00
] ,
) ;
2025-05-22 05:57:53 +00:00
2025-05-14 22:14:15 +00:00
const submitQuery = useCallback (
async ( query : PartListUnion ) = > {
2025-05-24 00:44:17 -07:00
if (
streamingState === StreamingState . Responding ||
streamingState === StreamingState . WaitingForConfirmation
)
return ;
2025-05-14 22:14:15 +00:00
const userMessageTimestamp = Date . now ( ) ;
setShowHelp ( false ) ;
2025-05-30 00:46:43 -07:00
abortControllerRef . current = new AbortController ( ) ;
2025-05-30 01:35:03 -07:00
const abortSignal = abortControllerRef . current . signal ;
2025-05-14 22:14:15 +00:00
const { queryToSend , shouldProceed } = await prepareQueryForGemini (
query ,
userMessageTimestamp ,
2025-05-30 01:35:03 -07:00
abortSignal ,
2025-05-14 22:14:15 +00:00
) ;
if ( ! shouldProceed || queryToSend === null ) {
return ;
}
2025-06-05 21:33:24 +00:00
if ( ! geminiClient ) {
2025-06-03 02:10:54 +00:00
const errorMsg = 'Gemini client is not available.' ;
setInitError ( errorMsg ) ;
addItem ( { type : MessageType . ERROR , text : errorMsg } , Date . now ( ) ) ;
2025-05-14 22:14:15 +00:00
return ;
}
2025-04-17 18:06:21 -04:00
2025-05-16 16:45:58 +00:00
setIsResponding ( true ) ;
2025-04-17 18:06:21 -04:00
setInitError ( null ) ;
2025-04-19 19:45:42 +01:00
2025-04-17 18:06:21 -04:00
try {
2025-06-05 21:33:24 +00:00
const stream = geminiClient . sendMessageStream ( queryToSend , abortSignal ) ;
2025-05-14 22:14:15 +00:00
const processingStatus = await processGeminiStreamEvents (
stream ,
userMessageTimestamp ,
2025-04-29 15:39:36 -07:00
) ;
2025-04-19 19:45:42 +01:00
2025-05-24 00:44:17 -07:00
if ( processingStatus === StreamProcessingStatus . UserCancelled ) {
2025-05-14 22:14:15 +00:00
return ;
2025-05-14 12:37:17 -07:00
}
2025-04-21 14:32:18 -04:00
2025-05-07 12:57:19 -07:00
if ( pendingHistoryItemRef . current ) {
addItem ( pendingHistoryItemRef . current , userMessageTimestamp ) ;
setPendingHistoryItem ( null ) ;
}
2025-04-18 17:47:49 -04:00
} catch ( error : unknown ) {
if ( ! isNodeError ( error ) || error . name !== 'AbortError' ) {
2025-05-06 16:20:28 -07:00
addItem (
2025-04-17 18:06:21 -04:00
{
2025-05-14 12:37:17 -07:00
type : MessageType . ERROR ,
2025-06-07 22:04:57 -07:00
text : parseAndFormatApiError (
getErrorMessage ( error ) || 'Unknown error' ,
) ,
2025-04-17 18:06:21 -04:00
} ,
2025-05-06 16:20:28 -07:00
userMessageTimestamp ,
2025-04-17 18:06:21 -04:00
) ;
Initial commit of Gemini Code CLI
This commit introduces the initial codebase for the Gemini Code CLI, a command-line interface designed to facilitate interaction with the Gemini API for software engineering tasks.
The code was migrated from a previous git repository as a single squashed commit.
Core Features & Components:
* **Gemini Integration:** Leverages the `@google/genai` SDK to interact with the Gemini models, supporting chat history, streaming responses, and function calling (tools).
* **Terminal UI:** Built with Ink (React for CLIs) providing an interactive chat interface within the terminal, including input prompts, message display, loading indicators, and tool interaction elements.
* **Tooling Framework:** Implements a robust tool system allowing Gemini to interact with the local environment. Includes tools for:
* File system listing (`ls`)
* File reading (`read-file`)
* Content searching (`grep`)
* File globbing (`glob`)
* File editing (`edit`)
* File writing (`write-file`)
* Executing bash commands (`terminal`)
* **State Management:** Handles the streaming state of Gemini responses and manages the conversation history.
* **Configuration:** Parses command-line arguments (`yargs`) and loads environment variables (`dotenv`) for setup.
* **Project Structure:** Organized into `core`, `ui`, `tools`, `config`, and `utils` directories using TypeScript. Includes basic build (`tsc`) and start scripts.
This initial version establishes the foundation for a powerful CLI tool enabling developers to use Gemini for coding assistance directly in their terminal environment.
---
Created by yours truly: __Gemini Code__
2025-04-15 21:41:08 -07:00
}
2025-04-17 18:06:21 -04:00
} finally {
2025-05-20 23:56:43 -07:00
abortControllerRef . current = null ; // Always reset
2025-05-16 16:45:58 +00:00
setIsResponding ( false ) ;
2025-04-21 14:32:18 -04:00
}
2025-04-17 18:06:21 -04:00
} ,
2025-04-19 19:45:42 +01:00
[
2025-06-01 14:16:24 -07:00
streamingState ,
2025-05-07 12:57:19 -07:00
setShowHelp ,
2025-05-24 00:44:17 -07:00
prepareQueryForGemini ,
processGeminiStreamEvents ,
pendingHistoryItemRef ,
2025-06-01 14:16:24 -07:00
addItem ,
setPendingHistoryItem ,
setInitError ,
2025-06-05 21:33:24 +00:00
geminiClient ,
2025-04-19 19:45:42 +01:00
] ,
2025-04-17 18:06:21 -04:00
) ;
2025-06-01 14:16:24 -07:00
/ * *
* Automatically submits responses for completed tool calls .
* This effect runs when ` toolCalls ` or ` isResponding ` changes .
* It ensures that tool responses are sent back to Gemini only when
* all processing for a given set of tools is finished and Gemini
* is not already generating a response .
* /
useEffect ( ( ) = > {
if ( isResponding ) {
return ;
}
const completedAndReadyToSubmitTools = toolCalls . filter (
(
tc : TrackedToolCall ,
) : tc is TrackedCompletedToolCall | TrackedCancelledToolCall = > {
const isTerminalState =
tc . status === 'success' ||
tc . status === 'error' ||
tc . status === 'cancelled' ;
if ( isTerminalState ) {
const completedOrCancelledCall = tc as
| TrackedCompletedToolCall
| TrackedCancelledToolCall ;
return (
! completedOrCancelledCall . responseSubmittedToGemini &&
completedOrCancelledCall . response ? . responseParts !== undefined
) ;
}
return false ;
} ,
) ;
2025-06-02 01:50:28 -07:00
if (
completedAndReadyToSubmitTools . length > 0 &&
completedAndReadyToSubmitTools . length === toolCalls . length
) {
2025-06-01 14:16:24 -07:00
const responsesToSend : PartListUnion [ ] =
completedAndReadyToSubmitTools . map (
( toolCall ) = > toolCall . response . responseParts ,
) ;
const callIdsToMarkAsSubmitted = completedAndReadyToSubmitTools . map (
( toolCall ) = > toolCall . request . callId ,
) ;
markToolsAsSubmitted ( callIdsToMarkAsSubmitted ) ;
submitQuery ( mergePartListUnions ( responsesToSend ) ) ;
}
} , [ toolCalls , isResponding , submitQuery , markToolsAsSubmitted , addItem ] ) ;
2025-05-22 05:57:53 +00:00
const pendingHistoryItems = [
pendingHistoryItemRef . current ,
2025-06-01 14:16:24 -07:00
pendingToolCallGroupDisplay ,
2025-05-22 05:57:53 +00:00
] . filter ( ( i ) = > i !== undefined && i !== null ) ;
2025-05-16 16:45:58 +00:00
2025-04-29 23:38:26 +00:00
return {
streamingState ,
submitQuery ,
initError ,
2025-05-22 05:57:53 +00:00
pendingHistoryItems ,
2025-04-29 23:38:26 +00:00
} ;
Initial commit of Gemini Code CLI
This commit introduces the initial codebase for the Gemini Code CLI, a command-line interface designed to facilitate interaction with the Gemini API for software engineering tasks.
The code was migrated from a previous git repository as a single squashed commit.
Core Features & Components:
* **Gemini Integration:** Leverages the `@google/genai` SDK to interact with the Gemini models, supporting chat history, streaming responses, and function calling (tools).
* **Terminal UI:** Built with Ink (React for CLIs) providing an interactive chat interface within the terminal, including input prompts, message display, loading indicators, and tool interaction elements.
* **Tooling Framework:** Implements a robust tool system allowing Gemini to interact with the local environment. Includes tools for:
* File system listing (`ls`)
* File reading (`read-file`)
* Content searching (`grep`)
* File globbing (`glob`)
* File editing (`edit`)
* File writing (`write-file`)
* Executing bash commands (`terminal`)
* **State Management:** Handles the streaming state of Gemini responses and manages the conversation history.
* **Configuration:** Parses command-line arguments (`yargs`) and loads environment variables (`dotenv`) for setup.
* **Project Structure:** Organized into `core`, `ui`, `tools`, `config`, and `utils` directories using TypeScript. Includes basic build (`tsc`) and start scripts.
This initial version establishes the foundation for a powerful CLI tool enabling developers to use Gemini for coding assistance directly in their terminal environment.
---
Created by yours truly: __Gemini Code__
2025-04-15 21:41:08 -07:00
} ;