diff options
| author | Taylor Mullen <[email protected]> | 2025-04-15 21:41:08 -0700 |
|---|---|---|
| committer | Taylor Mullen <[email protected]> | 2025-04-17 13:19:55 -0400 |
| commit | add233c5043264d47ecc6d3339a383f41a241ae8 (patch) | |
| tree | 3d80d412ed805007132cf44257bbd7667005dcd8 /packages/cli/src/core/geminiStreamProcessor.ts | |
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__
Diffstat (limited to 'packages/cli/src/core/geminiStreamProcessor.ts')
| -rw-r--r-- | packages/cli/src/core/geminiStreamProcessor.ts | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/packages/cli/src/core/geminiStreamProcessor.ts b/packages/cli/src/core/geminiStreamProcessor.ts new file mode 100644 index 00000000..12de49cb --- /dev/null +++ b/packages/cli/src/core/geminiStreamProcessor.ts @@ -0,0 +1,142 @@ +import { Part } from '@google/genai'; +import { HistoryItem } from '../ui/types.js'; +import { GeminiEventType, GeminiStream } from './GeminiStream.js'; +import { handleToolCallChunk, addErrorMessageToHistory } from './historyUpdater.js'; + +interface StreamProcessorParams { + stream: GeminiStream; + signal: AbortSignal; + setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>; + submitQuery: (query: Part) => Promise<void>, + getNextMessageId: () => number; + addHistoryItem: (itemData: Omit<HistoryItem, 'id'>, id: number) => void; + currentToolGroupIdRef: React.MutableRefObject<number | null>; +} + +/** + * Processes the Gemini stream, managing text buffering, adaptive rendering, + * and delegating history updates for tool calls and errors. + */ +export const processGeminiStream = async ({ // Renamed function for clarity + stream, + signal, + setHistory, + submitQuery, + getNextMessageId, + addHistoryItem, + currentToolGroupIdRef, +}: StreamProcessorParams): Promise<void> => { + // --- State specific to this stream processing invocation --- + let textBuffer = ''; + let renderTimeoutId: NodeJS.Timeout | null = null; + let isStreamComplete = false; + let currentGeminiMessageId: number | null = null; + + const render = (content: string) => { + if (currentGeminiMessageId === null) { + return; + } + setHistory(prev => prev.map(item => + item.id === currentGeminiMessageId && item.type === 'gemini' + ? { ...item, text: (item.text ?? '') + content } + : item + )); + } + // --- Adaptive Rendering Logic (nested) --- + const renderBufferedText = () => { + if (signal.aborted) { + if (renderTimeoutId) clearTimeout(renderTimeoutId); + renderTimeoutId = null; + return; + } + + const bufferLength = textBuffer.length; + let chunkSize = 0; + let delay = 50; + + if (bufferLength > 150) { + chunkSize = Math.min(bufferLength, 30); delay = 5; + } else if (bufferLength > 30) { + chunkSize = Math.min(bufferLength, 10); delay = 10; + } else if (bufferLength > 0) { + chunkSize = 2; delay = 20; + } + + if (chunkSize > 0) { + const chunkToRender = textBuffer.substring(0, chunkSize); + textBuffer = textBuffer.substring(chunkSize); + render(chunkToRender); + + renderTimeoutId = setTimeout(renderBufferedText, delay); + } else { + renderTimeoutId = null; // Clear timeout ID if nothing to render + if (!isStreamComplete) { + // Buffer empty, but stream might still send data, check again later + renderTimeoutId = setTimeout(renderBufferedText, 50); + } + } + }; + + const scheduleRender = () => { + if (renderTimeoutId === null) { + renderTimeoutId = setTimeout(renderBufferedText, 0); + } + }; + + // --- Stream Processing Loop --- + try { + for await (const chunk of stream) { + if (signal.aborted) break; + + if (chunk.type === GeminiEventType.Content) { + currentToolGroupIdRef.current = null; // Reset tool group on text + + if (currentGeminiMessageId === null) { + currentGeminiMessageId = getNextMessageId(); + addHistoryItem({ type: 'gemini', text: '' }, currentGeminiMessageId); + textBuffer = ''; + } + textBuffer += chunk.value; + scheduleRender(); + + } else if (chunk.type === GeminiEventType.ToolCallInfo) { + if (renderTimeoutId) { // Stop rendering loop + clearTimeout(renderTimeoutId); + renderTimeoutId = null; + } + + // Flush any text buffer content. + render(textBuffer); + currentGeminiMessageId = null; // End text message context + textBuffer = ''; // Clear buffer + + // Delegate history update for tool call + handleToolCallChunk( + chunk.value, + setHistory, + submitQuery, + getNextMessageId, + currentToolGroupIdRef + ); + } + } + if (signal.aborted) { + throw new Error("Request cancelled by user"); + } + } catch (error: any) { + if (renderTimeoutId) { // Ensure render loop stops on error + clearTimeout(renderTimeoutId); + renderTimeoutId = null; + } + // Delegate history update for error message + addErrorMessageToHistory(error, setHistory, getNextMessageId); + } finally { + isStreamComplete = true; // Signal stream end for render loop completion + if (renderTimeoutId) { + clearTimeout(renderTimeoutId); + renderTimeoutId = null; + } + + renderBufferedText(); // Force final render + } +};
\ No newline at end of file |
