summaryrefslogtreecommitdiff
path: root/packages/cli/src/core/geminiStreamProcessor.ts
diff options
context:
space:
mode:
authorTaylor Mullen <[email protected]>2025-04-15 21:41:08 -0700
committerTaylor Mullen <[email protected]>2025-04-17 13:19:55 -0400
commitadd233c5043264d47ecc6d3339a383f41a241ae8 (patch)
tree3d80d412ed805007132cf44257bbd7667005dcd8 /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.ts142
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