diff options
| author | N. Taylor Mullen <[email protected]> | 2025-06-01 16:11:37 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-06-01 23:11:37 +0000 |
| commit | 2828fc6d66cd7a74db231143183bd7c44e55148d (patch) | |
| tree | 849e95afc23b32e5f3745a5655fde1fe7e0c57d5 /packages/cli/src/nonInteractiveCli.ts | |
| parent | c51d6cc9d34bb3ff083f359cdd300502ea901ec8 (diff) | |
feat: Implement non-interactive mode for CLI (#675)
Diffstat (limited to 'packages/cli/src/nonInteractiveCli.ts')
| -rw-r--r-- | packages/cli/src/nonInteractiveCli.ts | 114 |
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); + } +} |
