diff options
Diffstat (limited to 'packages/cli/src/ui/hooks/passthroughCommandProcessor.ts')
| -rw-r--r-- | packages/cli/src/ui/hooks/passthroughCommandProcessor.ts | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/packages/cli/src/ui/hooks/passthroughCommandProcessor.ts b/packages/cli/src/ui/hooks/passthroughCommandProcessor.ts new file mode 100644 index 00000000..2a71c5ec --- /dev/null +++ b/packages/cli/src/ui/hooks/passthroughCommandProcessor.ts @@ -0,0 +1,108 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { exec as _exec } from 'child_process'; +import { useCallback } from 'react'; +import { Config } from '@gemini-code/server'; +import { type PartListUnion } from '@google/genai'; +import { HistoryItem, StreamingState } from '../types.js'; + +// Helper function (consider moving to a shared util if used elsewhere) +const addHistoryItem = ( + setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>, + itemData: Omit<HistoryItem, 'id'>, + id: number, +) => { + setHistory((prevHistory) => [ + ...prevHistory, + { ...itemData, id } as HistoryItem, + ]); +}; + +export const usePassthroughProcessor = ( + setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>, + setStreamingState: React.Dispatch<React.SetStateAction<StreamingState>>, + setDebugMessage: React.Dispatch<React.SetStateAction<string>>, + getNextMessageId: (baseTimestamp: number) => number, + config: Config, +) => { + const handlePassthroughCommand = useCallback( + (rawQuery: PartListUnion): boolean => { + if (typeof rawQuery !== 'string') { + return false; // Passthrough only works with string commands + } + + const trimmedQuery = rawQuery.trim(); + if (!trimmedQuery) { + return false; + } + + // Passthrough commands don't start with special characters like '/' or '@' + if (trimmedQuery.startsWith('/') || trimmedQuery.startsWith('@')) { + return false; + } + + const commandParts = trimmedQuery.split(/\s+/); + const commandName = commandParts[0]; + + if (config.getPassthroughCommands().includes(commandName)) { + // Add user message *before* execution starts + const userMessageTimestamp = Date.now(); + addHistoryItem( + setHistory, + { type: 'user', text: trimmedQuery }, + userMessageTimestamp, + ); + + // Execute and capture output + const targetDir = config.getTargetDir(); + setDebugMessage( + `Executing shell command in ${targetDir}: ${trimmedQuery}`, + ); + const execOptions = { + cwd: targetDir, + }; + + // Set state to Responding while the command runs + setStreamingState(StreamingState.Responding); + + _exec(trimmedQuery, execOptions, (error, stdout, stderr) => { + const timestamp = getNextMessageId(userMessageTimestamp); // Use user message time as base + if (error) { + addHistoryItem( + setHistory, + { type: 'error', text: error.message }, + timestamp, + ); + } else if (stderr) { + // Treat stderr as info for passthrough, as some tools use it for non-error output + addHistoryItem( + setHistory, + { type: 'info', text: stderr }, + timestamp, + ); + } else { + // Add stdout as an info message + addHistoryItem( + setHistory, + { type: 'info', text: stdout || '(Command produced no output)' }, + timestamp, + ); + } + // Set state back to Idle *after* command finishes and output is added + setStreamingState(StreamingState.Idle); + }); + + return true; // Command was handled + } + + return false; // Not a passthrough command + }, + [config, setDebugMessage, setHistory, setStreamingState, getNextMessageId], + ); + + return { handlePassthroughCommand }; +}; |
