summaryrefslogtreecommitdiff
path: root/packages/cli/src/nonInteractiveCli.ts
diff options
context:
space:
mode:
authorN. Taylor Mullen <[email protected]>2025-06-01 16:11:37 -0700
committerGitHub <[email protected]>2025-06-01 23:11:37 +0000
commit2828fc6d66cd7a74db231143183bd7c44e55148d (patch)
tree849e95afc23b32e5f3745a5655fde1fe7e0c57d5 /packages/cli/src/nonInteractiveCli.ts
parentc51d6cc9d34bb3ff083f359cdd300502ea901ec8 (diff)
feat: Implement non-interactive mode for CLI (#675)
Diffstat (limited to 'packages/cli/src/nonInteractiveCli.ts')
-rw-r--r--packages/cli/src/nonInteractiveCli.ts114
1 files changed, 114 insertions, 0 deletions
diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts
new file mode 100644
index 00000000..9077ecbf
--- /dev/null
+++ b/packages/cli/src/nonInteractiveCli.ts
@@ -0,0 +1,114 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {
+ Config,
+ GeminiClient,
+ ToolCallRequestInfo,
+ executeToolCall,
+ ToolRegistry,
+} from '@gemini-code/core';
+import {
+ Content,
+ Part,
+ FunctionCall,
+ GenerateContentResponse,
+} from '@google/genai';
+
+function getResponseText(response: GenerateContentResponse): string | null {
+ if (response.candidates && response.candidates.length > 0) {
+ const candidate = response.candidates[0];
+ if (
+ candidate.content &&
+ candidate.content.parts &&
+ candidate.content.parts.length > 0
+ ) {
+ return candidate.content.parts
+ .filter((part) => part.text)
+ .map((part) => part.text)
+ .join('');
+ }
+ }
+ return null;
+}
+
+export async function runNonInteractive(
+ config: Config,
+ input: string,
+): Promise<void> {
+ const geminiClient = new GeminiClient(config);
+ const toolRegistry: ToolRegistry = config.getToolRegistry();
+ await toolRegistry.discoverTools();
+
+ const chat = await geminiClient.startChat();
+ const abortController = new AbortController();
+ let currentMessages: Content[] = [{ role: 'user', parts: [{ text: input }] }];
+
+ try {
+ while (true) {
+ const functionCalls: FunctionCall[] = [];
+
+ const responseStream = await chat.sendMessageStream({
+ message: currentMessages[0]?.parts || [], // Ensure parts are always provided
+ config: {
+ abortSignal: abortController.signal,
+ tools: [
+ { functionDeclarations: toolRegistry.getFunctionDeclarations() },
+ ],
+ },
+ });
+
+ for await (const resp of responseStream) {
+ if (abortController.signal.aborted) {
+ console.error('Operation cancelled.');
+ return;
+ }
+ const textPart = getResponseText(resp);
+ if (textPart) {
+ process.stdout.write(textPart);
+ }
+ if (resp.functionCalls) {
+ functionCalls.push(...resp.functionCalls);
+ }
+ }
+
+ if (functionCalls.length > 0) {
+ const toolResponseParts: Part[] = [];
+
+ for (const fc of functionCalls) {
+ const callId = fc.id ?? `${fc.name}-${Date.now()}`;
+ const requestInfo: ToolCallRequestInfo = {
+ callId,
+ name: fc.name as string,
+ args: (fc.args ?? {}) as Record<string, unknown>,
+ };
+
+ const toolResponse = await executeToolCall(
+ requestInfo,
+ toolRegistry,
+ abortController.signal,
+ );
+
+ if (toolResponse.error) {
+ console.error(
+ `Error executing tool ${fc.name}: ${toolResponse.resultDisplay || toolResponse.error.message}`,
+ );
+ toolResponseParts.push(...(toolResponse.responseParts as Part[]));
+ } else {
+ toolResponseParts.push(...(toolResponse.responseParts as Part[]));
+ }
+ }
+ currentMessages = [{ role: 'user', parts: toolResponseParts }];
+ } else {
+ process.stdout.write('\n'); // Ensure a final newline
+ return;
+ }
+ }
+ } catch (error) {
+ console.error('Error processing input:', error);
+ process.exit(1);
+ }
+}