2025-04-18 17:44:24 -07:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright 2025 Google LLC
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
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 { useState, useRef, useCallback, useEffect } from 'react';
|
|
|
|
|
import { useInput } from 'ink';
|
2025-04-18 18:08:43 -04:00
|
|
|
import {
|
2025-04-19 19:45:42 +01:00
|
|
|
GeminiClient,
|
|
|
|
|
GeminiEventType as ServerGeminiEventType, // Rename to avoid conflict
|
|
|
|
|
getErrorMessage,
|
|
|
|
|
isNodeError,
|
2025-04-20 21:06:22 +01:00
|
|
|
Config,
|
2025-04-21 10:53:11 -04:00
|
|
|
ToolCallConfirmationDetails,
|
|
|
|
|
ToolCallResponseInfo,
|
2025-04-21 14:32:18 -04:00
|
|
|
ServerToolCallConfirmationDetails,
|
|
|
|
|
ToolConfirmationOutcome,
|
|
|
|
|
ToolResultDisplay,
|
|
|
|
|
ToolEditConfirmationDetails,
|
|
|
|
|
ToolExecuteConfirmationDetails,
|
2025-04-19 19:45:42 +01:00
|
|
|
} from '@gemini-code/server';
|
2025-04-22 11:01:09 -07:00
|
|
|
import { type Chat, type PartListUnion, type Part } from '@google/genai';
|
2025-04-19 19:45:42 +01:00
|
|
|
import {
|
2025-04-21 11:49:46 -07:00
|
|
|
StreamingState,
|
2025-04-19 19:45:42 +01:00
|
|
|
HistoryItem,
|
|
|
|
|
IndividualToolCallDisplay,
|
|
|
|
|
ToolCallStatus,
|
|
|
|
|
} from '../types.js';
|
2025-05-02 14:39:39 -07:00
|
|
|
import { isAtCommand } from '../utils/commandUtils.js';
|
2025-04-29 13:29:57 -07:00
|
|
|
import { useSlashCommandProcessor } from './slashCommandProcessor.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';
|
|
|
|
|
import { findSafeSplitPoint } from '../utils/markdownUtilities.js';
|
2025-04-28 12:38:07 -07: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
|
|
|
const addHistoryItem = (
|
2025-04-17 18:06:21 -04:00
|
|
|
setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>,
|
|
|
|
|
itemData: Omit<HistoryItem, 'id'>,
|
|
|
|
|
id: number,
|
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
|
|
|
setHistory((prevHistory) => [
|
|
|
|
|
...prevHistory,
|
|
|
|
|
{ ...itemData, id } as HistoryItem,
|
|
|
|
|
]);
|
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-19 19:45:42 +01:00
|
|
|
// Hook now accepts apiKey and model
|
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-04-17 18:06:21 -04:00
|
|
|
setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>,
|
2025-05-05 17:52:29 +00:00
|
|
|
refreshStatic: () => void,
|
2025-04-20 21:06:22 +01:00
|
|
|
config: Config,
|
2025-04-30 22:26:28 +00:00
|
|
|
openThemeDialog: () => void,
|
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-21 12:59:31 -07:00
|
|
|
const toolRegistry = config.getToolRegistry();
|
2025-04-17 18:06:21 -04:00
|
|
|
const [streamingState, setStreamingState] = useState<StreamingState>(
|
|
|
|
|
StreamingState.Idle,
|
|
|
|
|
);
|
2025-04-20 20:20:40 +01:00
|
|
|
const [debugMessage, setDebugMessage] = useState<string>('');
|
2025-04-17 18:06:21 -04:00
|
|
|
const [initError, setInitError] = useState<string | null>(null);
|
|
|
|
|
const abortControllerRef = useRef<AbortController | null>(null);
|
|
|
|
|
const chatSessionRef = useRef<Chat | null>(null);
|
|
|
|
|
const geminiClientRef = useRef<GeminiClient | null>(null);
|
|
|
|
|
const messageIdCounterRef = useRef(0);
|
2025-04-19 19:45:42 +01:00
|
|
|
const currentGeminiMessageIdRef = useRef<number | null>(null);
|
2025-04-17 18:06:21 -04:00
|
|
|
|
2025-04-29 13:29:57 -07:00
|
|
|
// ID Generation Callback
|
|
|
|
|
const getNextMessageId = useCallback((baseTimestamp: number): number => {
|
2025-04-29 15:39:36 -07:00
|
|
|
// Increment *before* adding to ensure uniqueness against the base timestamp
|
2025-04-29 13:29:57 -07:00
|
|
|
messageIdCounterRef.current += 1;
|
|
|
|
|
return baseTimestamp + messageIdCounterRef.current;
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// Instantiate command processors
|
2025-04-29 23:38:26 +00:00
|
|
|
const { handleSlashCommand, slashCommands } = useSlashCommandProcessor(
|
2025-04-29 13:29:57 -07:00
|
|
|
setHistory,
|
2025-05-05 17:52:29 +00:00
|
|
|
refreshStatic,
|
2025-04-29 13:29:57 -07:00
|
|
|
setDebugMessage,
|
|
|
|
|
getNextMessageId,
|
2025-04-30 22:26:28 +00:00
|
|
|
openThemeDialog,
|
2025-04-29 13:29:57 -07:00
|
|
|
);
|
|
|
|
|
|
2025-04-30 00:26:07 +00:00
|
|
|
const { handleShellCommand } = useShellCommandProcessor(
|
|
|
|
|
setHistory,
|
|
|
|
|
setStreamingState,
|
|
|
|
|
setDebugMessage,
|
|
|
|
|
getNextMessageId,
|
|
|
|
|
config,
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-19 19:45:42 +01:00
|
|
|
// Initialize Client Effect - uses props now
|
2025-04-17 18:06:21 -04:00
|
|
|
useEffect(() => {
|
|
|
|
|
setInitError(null);
|
|
|
|
|
if (!geminiClientRef.current) {
|
|
|
|
|
try {
|
2025-04-22 11:01:09 -07:00
|
|
|
geminiClientRef.current = new GeminiClient(config);
|
2025-04-18 17:47:49 -04:00
|
|
|
} catch (error: unknown) {
|
2025-04-17 18:06:21 -04:00
|
|
|
setInitError(
|
2025-04-18 17:47:49 -04:00
|
|
|
`Failed to initialize client: ${getErrorMessage(error) || 'Unknown error'}`,
|
2025-04-17 18:06:21 -04:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-20 21:06:22 +01:00
|
|
|
}, [config.getApiKey(), config.getModel()]);
|
2025-04-17 18:06:21 -04:00
|
|
|
|
|
|
|
|
// Input Handling Effect (remains the same)
|
|
|
|
|
useInput((input, key) => {
|
|
|
|
|
if (streamingState === StreamingState.Responding && key.escape) {
|
|
|
|
|
abortControllerRef.current?.abort();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-04-19 19:45:42 +01:00
|
|
|
// Helper function to update Gemini message content
|
|
|
|
|
const updateGeminiMessage = useCallback(
|
|
|
|
|
(messageId: number, newContent: string) => {
|
|
|
|
|
setHistory((prevHistory) =>
|
|
|
|
|
prevHistory.map((item) =>
|
|
|
|
|
item.id === messageId && item.type === 'gemini'
|
|
|
|
|
? { ...item, text: newContent }
|
|
|
|
|
: item,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
[setHistory],
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-25 17:11:08 -07:00
|
|
|
// Helper function to update Gemini message content
|
|
|
|
|
const updateAndAddGeminiMessageContent = useCallback(
|
|
|
|
|
(
|
|
|
|
|
messageId: number,
|
|
|
|
|
previousContent: string,
|
|
|
|
|
nextId: number,
|
|
|
|
|
nextContent: string,
|
|
|
|
|
) => {
|
|
|
|
|
setHistory((prevHistory) => {
|
|
|
|
|
const beforeNextHistory = prevHistory.map((item) =>
|
|
|
|
|
item.id === messageId ? { ...item, text: previousContent } : item,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
...beforeNextHistory,
|
|
|
|
|
{ id: nextId, type: 'gemini_content', text: nextContent },
|
|
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[setHistory],
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-19 19:45:42 +01:00
|
|
|
// Improved submit query function
|
2025-04-17 18:06:21 -04:00
|
|
|
const submitQuery = useCallback(
|
|
|
|
|
async (query: PartListUnion) => {
|
2025-04-19 19:45:42 +01:00
|
|
|
if (streamingState === StreamingState.Responding) return;
|
|
|
|
|
if (typeof query === 'string' && query.trim().length === 0) return;
|
2025-04-17 18:06:21 -04:00
|
|
|
|
2025-04-29 13:29:57 -07:00
|
|
|
const userMessageTimestamp = Date.now();
|
2025-04-29 15:39:36 -07:00
|
|
|
messageIdCounterRef.current = 0; // Reset counter for this new submission
|
|
|
|
|
let queryToSendToGemini: PartListUnion | null = null;
|
2025-04-29 13:29:57 -07:00
|
|
|
|
2025-04-20 20:20:40 +01:00
|
|
|
if (typeof query === 'string') {
|
2025-04-29 15:39:36 -07:00
|
|
|
const trimmedQuery = query.trim();
|
|
|
|
|
setDebugMessage(`User query: '${trimmedQuery}'`);
|
2025-04-25 00:12:20 +00:00
|
|
|
|
2025-04-30 00:26:07 +00:00
|
|
|
// 1. Check for Slash Commands (/)
|
2025-04-29 15:39:36 -07:00
|
|
|
if (handleSlashCommand(trimmedQuery)) {
|
2025-04-30 00:26:07 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Check for Shell Commands (! or $)
|
|
|
|
|
if (handleShellCommand(trimmedQuery)) {
|
|
|
|
|
return;
|
2025-04-29 13:29:57 -07:00
|
|
|
}
|
|
|
|
|
|
2025-04-29 15:39:36 -07:00
|
|
|
// 3. Check for @ Commands using the utility function
|
|
|
|
|
if (isAtCommand(trimmedQuery)) {
|
|
|
|
|
const atCommandResult = await handleAtCommand({
|
|
|
|
|
query: trimmedQuery,
|
|
|
|
|
config,
|
|
|
|
|
setHistory,
|
|
|
|
|
setDebugMessage,
|
|
|
|
|
getNextMessageId,
|
|
|
|
|
userMessageTimestamp,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!atCommandResult.shouldProceed) {
|
|
|
|
|
return; // @ command handled it (e.g., error) or decided not to proceed
|
|
|
|
|
}
|
|
|
|
|
queryToSendToGemini = atCommandResult.processedQuery;
|
|
|
|
|
// User message and tool UI were added by handleAtCommand
|
|
|
|
|
} else {
|
|
|
|
|
// 4. It's a normal query for Gemini
|
|
|
|
|
addHistoryItem(
|
|
|
|
|
setHistory,
|
|
|
|
|
{ type: 'user', text: trimmedQuery },
|
|
|
|
|
userMessageTimestamp,
|
|
|
|
|
);
|
|
|
|
|
queryToSendToGemini = trimmedQuery;
|
|
|
|
|
}
|
2025-04-29 13:29:57 -07:00
|
|
|
} else {
|
2025-04-29 15:39:36 -07:00
|
|
|
// 5. It's a function response (PartListUnion that isn't a string)
|
|
|
|
|
// Tool call/response UI handles history. Always proceed.
|
|
|
|
|
queryToSendToGemini = query;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Proceed to Gemini API call ---
|
|
|
|
|
if (queryToSendToGemini === null) {
|
|
|
|
|
// Should only happen if @ command failed and returned null query
|
|
|
|
|
setDebugMessage(
|
|
|
|
|
'Query processing resulted in null, not sending to Gemini.',
|
|
|
|
|
);
|
|
|
|
|
return;
|
2025-04-20 20:20:40 +01:00
|
|
|
}
|
|
|
|
|
|
2025-04-17 18:06:21 -04:00
|
|
|
const client = geminiClientRef.current;
|
|
|
|
|
if (!client) {
|
|
|
|
|
setInitError('Gemini client is not available.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!chatSessionRef.current) {
|
2025-04-19 19:45:42 +01:00
|
|
|
try {
|
2025-04-22 11:01:09 -07:00
|
|
|
chatSessionRef.current = await client.startChat();
|
2025-04-19 19:45:42 +01:00
|
|
|
} catch (err: unknown) {
|
|
|
|
|
setInitError(`Failed to start chat: ${getErrorMessage(err)}`);
|
|
|
|
|
setStreamingState(StreamingState.Idle);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-04-17 18:06:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setStreamingState(StreamingState.Responding);
|
|
|
|
|
setInitError(null);
|
|
|
|
|
const chat = chatSessionRef.current;
|
2025-04-19 19:45:42 +01:00
|
|
|
let currentToolGroupId: number | null = null;
|
|
|
|
|
|
2025-04-17 18:06:21 -04:00
|
|
|
try {
|
2025-04-19 19:45:42 +01:00
|
|
|
abortControllerRef.current = new AbortController();
|
|
|
|
|
const signal = abortControllerRef.current.signal;
|
|
|
|
|
|
2025-04-29 15:39:36 -07:00
|
|
|
// Use the determined query for the Gemini call
|
|
|
|
|
const stream = client.sendMessageStream(
|
|
|
|
|
chat,
|
|
|
|
|
queryToSendToGemini,
|
|
|
|
|
signal,
|
|
|
|
|
);
|
2025-04-19 19:45:42 +01:00
|
|
|
|
|
|
|
|
// Process the stream events from the server logic
|
|
|
|
|
let currentGeminiText = ''; // To accumulate message content
|
|
|
|
|
let hasInitialGeminiResponse = false;
|
|
|
|
|
|
|
|
|
|
for await (const event of stream) {
|
|
|
|
|
if (signal.aborted) break;
|
|
|
|
|
|
|
|
|
|
if (event.type === ServerGeminiEventType.Content) {
|
|
|
|
|
// For content events, accumulate the text and update an existing message or create a new one
|
|
|
|
|
currentGeminiText += event.value;
|
|
|
|
|
|
2025-04-22 09:07:19 -04:00
|
|
|
// Reset group because we're now adding a user message to the history. If we didn't reset the
|
|
|
|
|
// group here then any subsequent tool calls would get grouped before this message resulting in
|
|
|
|
|
// a misordering of history.
|
|
|
|
|
currentToolGroupId = null;
|
|
|
|
|
|
2025-04-19 19:45:42 +01:00
|
|
|
if (!hasInitialGeminiResponse) {
|
|
|
|
|
// Create a new Gemini message if this is the first content event
|
|
|
|
|
hasInitialGeminiResponse = true;
|
|
|
|
|
const eventTimestamp = getNextMessageId(userMessageTimestamp);
|
|
|
|
|
currentGeminiMessageIdRef.current = eventTimestamp;
|
2025-04-19 11:07:39 +01:00
|
|
|
|
|
|
|
|
addHistoryItem(
|
|
|
|
|
setHistory,
|
2025-04-19 19:45:42 +01:00
|
|
|
{ type: 'gemini', text: currentGeminiText },
|
|
|
|
|
eventTimestamp,
|
2025-04-19 11:07:39 +01:00
|
|
|
);
|
2025-04-19 19:45:42 +01:00
|
|
|
} else if (currentGeminiMessageIdRef.current !== null) {
|
2025-04-25 17:11:08 -07:00
|
|
|
const splitPoint = findSafeSplitPoint(currentGeminiText);
|
|
|
|
|
|
|
|
|
|
if (splitPoint === currentGeminiText.length) {
|
|
|
|
|
// Update the existing message with accumulated content
|
|
|
|
|
updateGeminiMessage(
|
|
|
|
|
currentGeminiMessageIdRef.current,
|
|
|
|
|
currentGeminiText,
|
|
|
|
|
);
|
|
|
|
|
} 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 originalMessageRef = currentGeminiMessageIdRef.current;
|
|
|
|
|
const beforeText = currentGeminiText.substring(0, splitPoint);
|
|
|
|
|
|
|
|
|
|
currentGeminiMessageIdRef.current =
|
|
|
|
|
getNextMessageId(userMessageTimestamp);
|
|
|
|
|
const afterText = currentGeminiText.substring(splitPoint);
|
|
|
|
|
currentGeminiText = afterText;
|
|
|
|
|
updateAndAddGeminiMessageContent(
|
|
|
|
|
originalMessageRef,
|
|
|
|
|
beforeText,
|
|
|
|
|
currentGeminiMessageIdRef.current,
|
|
|
|
|
afterText,
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-04-19 19:45:42 +01:00
|
|
|
}
|
|
|
|
|
} else if (event.type === ServerGeminiEventType.ToolCallRequest) {
|
|
|
|
|
// Reset the Gemini message tracking for the next response
|
|
|
|
|
currentGeminiText = '';
|
|
|
|
|
hasInitialGeminiResponse = false;
|
|
|
|
|
currentGeminiMessageIdRef.current = null;
|
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-19 19:45:42 +01:00
|
|
|
const { callId, name, args } = event.value;
|
|
|
|
|
|
|
|
|
|
const cliTool = toolRegistry.getTool(name); // Get the full CLI tool
|
|
|
|
|
if (!cliTool) {
|
|
|
|
|
console.error(`CLI Tool "${name}" not found!`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-04-17 18:06:21 -04:00
|
|
|
|
2025-04-19 19:45:42 +01:00
|
|
|
if (currentToolGroupId === null) {
|
|
|
|
|
currentToolGroupId = getNextMessageId(userMessageTimestamp);
|
|
|
|
|
// Add explicit cast to Omit<HistoryItem, 'id'>
|
|
|
|
|
addHistoryItem(
|
|
|
|
|
setHistory,
|
|
|
|
|
{ type: 'tool_group', tools: [] } as Omit<HistoryItem, 'id'>,
|
|
|
|
|
currentToolGroupId,
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-04-17 18:06:21 -04:00
|
|
|
|
2025-04-21 10:53:11 -04:00
|
|
|
let description: string;
|
|
|
|
|
try {
|
|
|
|
|
description = cliTool.getDescription(args);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
description = `Error: Unable to get description: ${getErrorMessage(e)}`;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-19 19:45:42 +01:00
|
|
|
// Create the UI display object matching IndividualToolCallDisplay
|
|
|
|
|
const toolCallDisplay: IndividualToolCallDisplay = {
|
|
|
|
|
callId,
|
2025-04-22 07:48:12 -04:00
|
|
|
name: cliTool.displayName,
|
2025-04-21 10:53:11 -04:00
|
|
|
description,
|
2025-04-19 19:45:42 +01:00
|
|
|
status: ToolCallStatus.Pending,
|
|
|
|
|
resultDisplay: undefined,
|
|
|
|
|
confirmationDetails: undefined,
|
|
|
|
|
};
|
2025-04-17 18:06:21 -04:00
|
|
|
|
2025-04-19 19:45:42 +01:00
|
|
|
// Add pending tool call to the UI history group
|
|
|
|
|
setHistory((prevHistory) =>
|
|
|
|
|
prevHistory.map((item) => {
|
|
|
|
|
if (
|
|
|
|
|
item.id === currentToolGroupId &&
|
|
|
|
|
item.type === 'tool_group'
|
|
|
|
|
) {
|
|
|
|
|
// Ensure item.tools exists and is an array before spreading
|
|
|
|
|
const currentTools = Array.isArray(item.tools)
|
|
|
|
|
? item.tools
|
|
|
|
|
: [];
|
|
|
|
|
return {
|
|
|
|
|
...item,
|
|
|
|
|
tools: [...currentTools, toolCallDisplay], // Add the complete display object
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return item;
|
|
|
|
|
}),
|
|
|
|
|
);
|
2025-04-21 10:53:11 -04:00
|
|
|
} else if (event.type === ServerGeminiEventType.ToolCallResponse) {
|
2025-04-21 14:32:18 -04:00
|
|
|
const status = event.value.error
|
|
|
|
|
? ToolCallStatus.Error
|
|
|
|
|
: ToolCallStatus.Success;
|
|
|
|
|
updateFunctionResponseUI(event.value, status);
|
2025-04-21 10:53:11 -04:00
|
|
|
} else if (
|
|
|
|
|
event.type === ServerGeminiEventType.ToolCallConfirmation
|
|
|
|
|
) {
|
2025-04-21 14:32:18 -04:00
|
|
|
const confirmationDetails = wireConfirmationSubmission(event.value);
|
|
|
|
|
updateConfirmingFunctionStatusUI(
|
|
|
|
|
event.value.request.callId,
|
|
|
|
|
confirmationDetails,
|
2025-04-19 19:45:42 +01:00
|
|
|
);
|
2025-04-21 10:53:11 -04:00
|
|
|
setStreamingState(StreamingState.WaitingForConfirmation);
|
|
|
|
|
return;
|
2025-04-19 19:45:42 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-04-21 14:32:18 -04:00
|
|
|
|
|
|
|
|
setStreamingState(StreamingState.Idle);
|
2025-04-18 17:47:49 -04:00
|
|
|
} catch (error: unknown) {
|
|
|
|
|
if (!isNodeError(error) || error.name !== 'AbortError') {
|
2025-04-19 19:45:42 +01:00
|
|
|
console.error('Error processing stream or executing tool:', error);
|
2025-04-17 18:06:21 -04:00
|
|
|
addHistoryItem(
|
|
|
|
|
setHistory,
|
|
|
|
|
{
|
|
|
|
|
type: 'error',
|
2025-04-19 19:45:42 +01:00
|
|
|
text: `[Error: ${getErrorMessage(error)}]`,
|
2025-04-17 18:06:21 -04:00
|
|
|
},
|
|
|
|
|
getNextMessageId(userMessageTimestamp),
|
|
|
|
|
);
|
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-21 14:32:18 -04:00
|
|
|
setStreamingState(StreamingState.Idle);
|
2025-04-17 18:06:21 -04:00
|
|
|
} finally {
|
|
|
|
|
abortControllerRef.current = null;
|
|
|
|
|
}
|
2025-04-21 10:53:11 -04:00
|
|
|
|
2025-04-21 14:32:18 -04:00
|
|
|
function updateConfirmingFunctionStatusUI(
|
|
|
|
|
callId: string,
|
|
|
|
|
confirmationDetails: ToolCallConfirmationDetails | undefined,
|
|
|
|
|
) {
|
|
|
|
|
setHistory((prevHistory) =>
|
|
|
|
|
prevHistory.map((item) => {
|
|
|
|
|
if (item.id === currentToolGroupId && item.type === 'tool_group') {
|
|
|
|
|
return {
|
|
|
|
|
...item,
|
|
|
|
|
tools: item.tools.map((tool) =>
|
|
|
|
|
tool.callId === callId
|
|
|
|
|
? {
|
|
|
|
|
...tool,
|
|
|
|
|
status: ToolCallStatus.Confirming,
|
|
|
|
|
confirmationDetails,
|
|
|
|
|
}
|
|
|
|
|
: tool,
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return item;
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateFunctionResponseUI(
|
|
|
|
|
toolResponse: ToolCallResponseInfo,
|
|
|
|
|
status: ToolCallStatus,
|
|
|
|
|
) {
|
2025-04-21 10:53:11 -04:00
|
|
|
setHistory((prevHistory) =>
|
|
|
|
|
prevHistory.map((item) => {
|
|
|
|
|
if (item.id === currentToolGroupId && item.type === 'tool_group') {
|
|
|
|
|
return {
|
|
|
|
|
...item,
|
|
|
|
|
tools: item.tools.map((tool) => {
|
|
|
|
|
if (tool.callId === toolResponse.callId) {
|
|
|
|
|
return {
|
|
|
|
|
...tool,
|
2025-04-21 14:32:18 -04:00
|
|
|
status,
|
2025-04-21 10:53:11 -04:00
|
|
|
resultDisplay: toolResponse.resultDisplay,
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
return tool;
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return item;
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-04-21 14:32:18 -04:00
|
|
|
|
|
|
|
|
function wireConfirmationSubmission(
|
|
|
|
|
confirmationDetails: ServerToolCallConfirmationDetails,
|
|
|
|
|
): ToolCallConfirmationDetails {
|
|
|
|
|
const originalConfirmationDetails = confirmationDetails.details;
|
|
|
|
|
const request = confirmationDetails.request;
|
|
|
|
|
const resubmittingConfirm = async (
|
|
|
|
|
outcome: ToolConfirmationOutcome,
|
|
|
|
|
) => {
|
|
|
|
|
originalConfirmationDetails.onConfirm(outcome);
|
|
|
|
|
|
|
|
|
|
if (outcome === ToolConfirmationOutcome.Cancel) {
|
|
|
|
|
let resultDisplay: ToolResultDisplay | undefined;
|
|
|
|
|
if ('fileDiff' in originalConfirmationDetails) {
|
|
|
|
|
resultDisplay = {
|
|
|
|
|
fileDiff: (
|
|
|
|
|
originalConfirmationDetails as ToolEditConfirmationDetails
|
|
|
|
|
).fileDiff,
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
resultDisplay = `~~${(originalConfirmationDetails as ToolExecuteConfirmationDetails).command}~~`;
|
|
|
|
|
}
|
|
|
|
|
const functionResponse: Part = {
|
|
|
|
|
functionResponse: {
|
|
|
|
|
id: request.callId,
|
|
|
|
|
name: request.name,
|
|
|
|
|
response: { error: 'User rejected function call.' },
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const responseInfo: ToolCallResponseInfo = {
|
|
|
|
|
callId: request.callId,
|
|
|
|
|
responsePart: functionResponse,
|
|
|
|
|
resultDisplay,
|
|
|
|
|
error: undefined,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
updateFunctionResponseUI(responseInfo, ToolCallStatus.Error);
|
2025-04-25 17:11:08 -07:00
|
|
|
setStreamingState(StreamingState.Idle);
|
2025-04-21 14:32:18 -04:00
|
|
|
} else {
|
|
|
|
|
const tool = toolRegistry.getTool(request.name);
|
|
|
|
|
if (!tool) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Tool "${request.name}" not found or is not registered.`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const result = await tool.execute(request.args);
|
|
|
|
|
const functionResponse: Part = {
|
|
|
|
|
functionResponse: {
|
|
|
|
|
name: request.name,
|
|
|
|
|
id: request.callId,
|
|
|
|
|
response: { output: result.llmContent },
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const responseInfo: ToolCallResponseInfo = {
|
|
|
|
|
callId: request.callId,
|
|
|
|
|
responsePart: functionResponse,
|
|
|
|
|
resultDisplay: result.returnDisplay,
|
|
|
|
|
error: undefined,
|
|
|
|
|
};
|
|
|
|
|
updateFunctionResponseUI(responseInfo, ToolCallStatus.Success);
|
2025-04-25 17:11:08 -07:00
|
|
|
setStreamingState(StreamingState.Idle);
|
2025-04-21 14:32:18 -04:00
|
|
|
await submitQuery(functionResponse);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...originalConfirmationDetails,
|
|
|
|
|
onConfirm: resubmittingConfirm,
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-04-17 18:06:21 -04:00
|
|
|
},
|
2025-04-29 13:29:57 -07:00
|
|
|
// Dependencies need careful review
|
2025-04-19 19:45:42 +01:00
|
|
|
[
|
|
|
|
|
streamingState,
|
|
|
|
|
setHistory,
|
2025-04-29 13:29:57 -07:00
|
|
|
config,
|
2025-04-19 19:45:42 +01:00
|
|
|
getNextMessageId,
|
|
|
|
|
updateGeminiMessage,
|
2025-04-29 13:29:57 -07:00
|
|
|
handleSlashCommand,
|
2025-04-29 15:39:36 -07:00
|
|
|
// handleAtCommand is implicitly included via its direct call
|
|
|
|
|
setDebugMessage, // Added dependency for handleAtCommand & passthrough
|
|
|
|
|
setStreamingState, // Added dependency for handlePassthroughCommand
|
2025-04-29 13:29:57 -07:00
|
|
|
updateAndAddGeminiMessageContent,
|
2025-04-19 19:45:42 +01:00
|
|
|
],
|
2025-04-17 18:06:21 -04:00
|
|
|
);
|
|
|
|
|
|
2025-04-29 23:38:26 +00:00
|
|
|
return {
|
|
|
|
|
streamingState,
|
|
|
|
|
submitQuery,
|
|
|
|
|
initError,
|
|
|
|
|
debugMessage,
|
|
|
|
|
slashCommands,
|
|
|
|
|
};
|
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
|
|
|
};
|