summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks/passthroughCommandProcessor.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/hooks/passthroughCommandProcessor.ts')
-rw-r--r--packages/cli/src/ui/hooks/passthroughCommandProcessor.ts108
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 };
+};