summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks/atCommandProcessor.ts
diff options
context:
space:
mode:
authorAllen Hutchison <[email protected]>2025-04-29 08:29:09 -0700
committerGitHub <[email protected]>2025-04-29 08:29:09 -0700
commite0de69f3846ecf9de40bb2e02546235d9db53393 (patch)
treec3c190b6ae0e555ebd01c27cf1d9a860fe2b61dc /packages/cli/src/ui/hooks/atCommandProcessor.ts
parentdf44ffbcffaebbb508147f038d8f21f78ee88e33 (diff)
First four independent files for @ commands. (#205)
Diffstat (limited to 'packages/cli/src/ui/hooks/atCommandProcessor.ts')
-rw-r--r--packages/cli/src/ui/hooks/atCommandProcessor.ts183
1 files changed, 183 insertions, 0 deletions
diff --git a/packages/cli/src/ui/hooks/atCommandProcessor.ts b/packages/cli/src/ui/hooks/atCommandProcessor.ts
new file mode 100644
index 00000000..314c969d
--- /dev/null
+++ b/packages/cli/src/ui/hooks/atCommandProcessor.ts
@@ -0,0 +1,183 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { PartListUnion } from '@google/genai';
+import { Config, getErrorMessage } from '@gemini-code/server';
+import {
+ HistoryItem,
+ IndividualToolCallDisplay,
+ ToolCallStatus,
+} from '../types.js';
+
+// Helper function to add history items (could be moved to a shared util if needed elsewhere)
+const addHistoryItem = (
+ setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>,
+ itemData: Omit<HistoryItem, 'id'>,
+ id: number,
+) => {
+ setHistory((prevHistory) => [
+ ...prevHistory,
+ { ...itemData, id } as HistoryItem,
+ ]);
+};
+
+interface HandleAtCommandParams {
+ query: string; // Raw user input
+ config: Config;
+ setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>;
+ setDebugMessage: React.Dispatch<React.SetStateAction<string>>;
+ getNextMessageId: (baseTimestamp: number) => number;
+ userMessageTimestamp: number;
+}
+
+interface HandleAtCommandResult {
+ processedQuery: PartListUnion; // Query to potentially send to Gemini
+ shouldProceed: boolean; // Whether the main hook should continue processing
+}
+
+/**
+ * Processes user input that might start with the '@' command to read files/directories.
+ * If it's an '@' command, it attempts to read the specified path, updates the UI
+ * with the tool call status, and prepares the query to be sent to the LLM.
+ *
+ * @returns An object containing the potentially modified query and a flag
+ * indicating if the main hook should proceed with the Gemini API call.
+ */
+export async function handleAtCommand({
+ query,
+ config,
+ setHistory,
+ setDebugMessage,
+ getNextMessageId,
+ userMessageTimestamp,
+}: HandleAtCommandParams): Promise<HandleAtCommandResult> {
+ const trimmedQuery = query.trim();
+
+ if (!trimmedQuery.startsWith('@')) {
+ // Not an '@' command, proceed as normal
+ // Add the user message here before returning
+ addHistoryItem(
+ setHistory,
+ { type: 'user', text: query },
+ userMessageTimestamp,
+ );
+ // Use property shorthand for processedQuery
+ return { processedQuery: query, shouldProceed: true };
+ }
+
+ // --- It is an '@' command ---
+ const filePath = trimmedQuery.substring(1);
+
+ if (!filePath) {
+ // Handle case where it's just "@" - treat as normal input
+ addHistoryItem(
+ setHistory,
+ { type: 'user', text: query },
+ userMessageTimestamp,
+ );
+ // Use property shorthand for processedQuery
+ return { processedQuery: query, shouldProceed: true }; // Send the "@" to the model
+ }
+
+ const toolRegistry = config.getToolRegistry();
+ const readManyFilesTool = toolRegistry.getTool('read_many_files');
+
+ // Add user message first, so it appears before potential errors/tool UI
+ addHistoryItem(
+ setHistory,
+ { type: 'user', text: query },
+ userMessageTimestamp,
+ );
+
+ if (!readManyFilesTool) {
+ const errorTimestamp = getNextMessageId(userMessageTimestamp);
+ addHistoryItem(
+ setHistory,
+ { type: 'error', text: 'Error: read_many_files tool not found.' },
+ errorTimestamp,
+ );
+ // Use property shorthand for processedQuery
+ return { processedQuery: query, shouldProceed: false }; // Don't proceed if tool is missing
+ }
+
+ // --- Path Handling for @ command ---
+ let pathSpec = filePath;
+ // Basic check: If no extension or ends with '/', assume directory and add globstar.
+ if (!filePath.includes('.') || filePath.endsWith('/')) {
+ pathSpec = filePath.endsWith('/') ? `${filePath}**` : `${filePath}/**`;
+ }
+ const toolArgs = { paths: [pathSpec] };
+ const contentLabel =
+ pathSpec === filePath ? filePath : `directory ${filePath}`; // Adjust label
+ // --- End Path Handling ---
+
+ let toolCallDisplay: IndividualToolCallDisplay;
+ let processedQuery: PartListUnion = query; // Default to original query
+
+ try {
+ setDebugMessage(`Reading via @ command: ${pathSpec}`);
+ const result = await readManyFilesTool.execute(toolArgs);
+ const fileContent = result.llmContent || '';
+
+ // Construct success UI
+ toolCallDisplay = {
+ callId: `client-read-${userMessageTimestamp}`,
+ name: readManyFilesTool.displayName,
+ description: readManyFilesTool.getDescription(toolArgs),
+ status: ToolCallStatus.Success,
+ resultDisplay: result.returnDisplay,
+ confirmationDetails: undefined,
+ };
+
+ // Prepend file content to the query sent to the model
+ processedQuery = [
+ {
+ text: `--- Content from: ${contentLabel} ---
+${fileContent}
+--- End Content ---`,
+ },
+ // TODO: Handle cases like "@README.md explain this" by appending the rest of the query
+ ];
+
+ // Add the tool group UI
+ const toolGroupId = getNextMessageId(userMessageTimestamp);
+ addHistoryItem(
+ setHistory,
+ { type: 'tool_group', tools: [toolCallDisplay] } as Omit<
+ HistoryItem,
+ 'id'
+ >,
+ toolGroupId,
+ );
+
+ // Use property shorthand for processedQuery
+ return { processedQuery, shouldProceed: true }; // Proceed to Gemini
+ } catch (error) {
+ // Construct error UI
+ toolCallDisplay = {
+ callId: `client-read-${userMessageTimestamp}`,
+ name: readManyFilesTool.displayName,
+ description: readManyFilesTool.getDescription(toolArgs),
+ status: ToolCallStatus.Error,
+ resultDisplay: `Error reading ${contentLabel}: ${getErrorMessage(error)}`,
+ confirmationDetails: undefined,
+ };
+
+ // Add the tool group UI and signal not to proceed
+ const toolGroupId = getNextMessageId(userMessageTimestamp);
+ addHistoryItem(
+ setHistory,
+ { type: 'tool_group', tools: [toolCallDisplay] } as Omit<
+ HistoryItem,
+ 'id'
+ >,
+ toolGroupId,
+ );
+
+ // Use property shorthand for processedQuery
+ return { processedQuery: query, shouldProceed: false }; // Don't proceed on error
+ }
+}