2025-06-01 16:11:37 -07:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright 2025 Google LLC
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
2025-08-26 00:04:53 +02:00
|
|
|
import type { Config, ToolCallRequestInfo } from '@google/gemini-cli-core';
|
2025-09-19 13:49:35 +00:00
|
|
|
import { isSlashCommand } from './ui/utils/commandUtils.js';
|
|
|
|
|
import type { LoadedSettings } from './config/settings.js';
|
2025-06-01 16:11:37 -07:00
|
|
|
import {
|
|
|
|
|
executeToolCall,
|
2025-06-13 03:44:17 -04:00
|
|
|
shutdownTelemetry,
|
|
|
|
|
isTelemetrySdkInitialized,
|
2025-07-31 05:36:12 -07:00
|
|
|
GeminiEventType,
|
2025-08-25 21:44:45 -07:00
|
|
|
FatalInputError,
|
2025-09-09 01:14:15 -04:00
|
|
|
promptIdContext,
|
2025-09-11 05:19:47 +09:00
|
|
|
OutputFormat,
|
|
|
|
|
JsonFormatter,
|
|
|
|
|
uiTelemetryService,
|
2025-06-25 05:41:11 -07:00
|
|
|
} from '@google/gemini-cli-core';
|
2025-09-19 13:49:35 +00:00
|
|
|
|
2025-08-26 00:04:53 +02:00
|
|
|
import type { Content, Part } from '@google/genai';
|
2025-06-01 16:11:37 -07:00
|
|
|
|
2025-09-19 13:49:35 +00:00
|
|
|
import { handleSlashCommand } from './nonInteractiveCliCommands.js';
|
2025-08-05 16:11:21 -07:00
|
|
|
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
|
2025-08-21 14:47:40 -04:00
|
|
|
import { handleAtCommand } from './ui/hooks/atCommandProcessor.js';
|
2025-09-11 05:19:47 +09:00
|
|
|
import {
|
|
|
|
|
handleError,
|
|
|
|
|
handleToolError,
|
|
|
|
|
handleCancellationError,
|
|
|
|
|
handleMaxTurnsExceededError,
|
|
|
|
|
} from './utils/errors.js';
|
2025-06-23 17:30:13 -04:00
|
|
|
|
2025-06-01 16:11:37 -07:00
|
|
|
export async function runNonInteractive(
|
|
|
|
|
config: Config,
|
2025-09-19 13:49:35 +00:00
|
|
|
settings: LoadedSettings,
|
2025-06-01 16:11:37 -07:00
|
|
|
input: string,
|
2025-07-10 00:19:30 +05:30
|
|
|
prompt_id: string,
|
2025-06-01 16:11:37 -07:00
|
|
|
): Promise<void> {
|
2025-09-09 01:14:15 -04:00
|
|
|
return promptIdContext.run(prompt_id, async () => {
|
|
|
|
|
const consolePatcher = new ConsolePatcher({
|
|
|
|
|
stderr: true,
|
|
|
|
|
debugMode: config.getDebugMode(),
|
2025-08-21 14:47:40 -04:00
|
|
|
});
|
|
|
|
|
|
2025-09-09 01:14:15 -04:00
|
|
|
try {
|
|
|
|
|
consolePatcher.patch();
|
|
|
|
|
// Handle EPIPE errors when the output is piped to a command that closes early.
|
|
|
|
|
process.stdout.on('error', (err: NodeJS.ErrnoException) => {
|
|
|
|
|
if (err.code === 'EPIPE') {
|
|
|
|
|
// Exit gracefully if the pipe is closed.
|
|
|
|
|
process.exit(0);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const geminiClient = config.getGeminiClient();
|
|
|
|
|
|
|
|
|
|
const abortController = new AbortController();
|
|
|
|
|
|
2025-09-19 13:49:35 +00:00
|
|
|
let query: Part[] | undefined;
|
2025-09-09 01:14:15 -04:00
|
|
|
|
2025-09-19 13:49:35 +00:00
|
|
|
if (isSlashCommand(input)) {
|
|
|
|
|
const slashCommandResult = await handleSlashCommand(
|
|
|
|
|
input,
|
|
|
|
|
abortController,
|
|
|
|
|
config,
|
|
|
|
|
settings,
|
2025-07-11 07:55:03 -07:00
|
|
|
);
|
2025-09-19 13:49:35 +00:00
|
|
|
// If a slash command is found and returns a prompt, use it.
|
|
|
|
|
// Otherwise, slashCommandResult fall through to the default prompt
|
|
|
|
|
// handling.
|
|
|
|
|
if (slashCommandResult) {
|
|
|
|
|
query = slashCommandResult as Part[];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!query) {
|
|
|
|
|
const { processedQuery, shouldProceed } = await handleAtCommand({
|
|
|
|
|
query: input,
|
|
|
|
|
config,
|
|
|
|
|
addItem: (_item, _timestamp) => 0,
|
|
|
|
|
onDebugMessage: () => {},
|
|
|
|
|
messageId: Date.now(),
|
|
|
|
|
signal: abortController.signal,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!shouldProceed || !processedQuery) {
|
|
|
|
|
// An error occurred during @include processing (e.g., file not found).
|
|
|
|
|
// The error message is already logged by handleAtCommand.
|
|
|
|
|
throw new FatalInputError(
|
|
|
|
|
'Exiting due to an error processing the @ command.',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
query = processedQuery as Part[];
|
2025-07-11 07:55:03 -07:00
|
|
|
}
|
2025-06-01 16:11:37 -07:00
|
|
|
|
2025-09-19 13:49:35 +00:00
|
|
|
let currentMessages: Content[] = [{ role: 'user', parts: query }];
|
2025-09-09 01:14:15 -04:00
|
|
|
|
|
|
|
|
let turnCount = 0;
|
|
|
|
|
while (true) {
|
|
|
|
|
turnCount++;
|
|
|
|
|
if (
|
|
|
|
|
config.getMaxSessionTurns() >= 0 &&
|
|
|
|
|
turnCount > config.getMaxSessionTurns()
|
|
|
|
|
) {
|
2025-09-11 05:19:47 +09:00
|
|
|
handleMaxTurnsExceededError(config);
|
2025-06-01 16:11:37 -07:00
|
|
|
}
|
2025-09-09 01:14:15 -04:00
|
|
|
const toolCallRequests: ToolCallRequestInfo[] = [];
|
2025-07-31 05:36:12 -07:00
|
|
|
|
2025-09-09 01:14:15 -04:00
|
|
|
const responseStream = geminiClient.sendMessageStream(
|
|
|
|
|
currentMessages[0]?.parts || [],
|
|
|
|
|
abortController.signal,
|
|
|
|
|
prompt_id,
|
|
|
|
|
);
|
2025-06-01 16:11:37 -07:00
|
|
|
|
2025-09-11 05:19:47 +09:00
|
|
|
let responseText = '';
|
2025-09-09 01:14:15 -04:00
|
|
|
for await (const event of responseStream) {
|
|
|
|
|
if (abortController.signal.aborted) {
|
2025-09-11 05:19:47 +09:00
|
|
|
handleCancellationError(config);
|
2025-09-09 01:14:15 -04:00
|
|
|
}
|
2025-06-01 16:11:37 -07:00
|
|
|
|
2025-09-09 01:14:15 -04:00
|
|
|
if (event.type === GeminiEventType.Content) {
|
2025-09-11 05:19:47 +09:00
|
|
|
if (config.getOutputFormat() === OutputFormat.JSON) {
|
|
|
|
|
responseText += event.value;
|
|
|
|
|
} else {
|
|
|
|
|
process.stdout.write(event.value);
|
|
|
|
|
}
|
2025-09-09 01:14:15 -04:00
|
|
|
} else if (event.type === GeminiEventType.ToolCallRequest) {
|
|
|
|
|
toolCallRequests.push(event.value);
|
2025-06-04 23:25:57 -07:00
|
|
|
}
|
2025-09-09 01:14:15 -04:00
|
|
|
}
|
2025-06-04 23:25:57 -07:00
|
|
|
|
2025-09-09 01:14:15 -04:00
|
|
|
if (toolCallRequests.length > 0) {
|
|
|
|
|
const toolResponseParts: Part[] = [];
|
|
|
|
|
for (const requestInfo of toolCallRequests) {
|
|
|
|
|
const toolResponse = await executeToolCall(
|
|
|
|
|
config,
|
|
|
|
|
requestInfo,
|
|
|
|
|
abortController.signal,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (toolResponse.error) {
|
2025-09-11 05:19:47 +09:00
|
|
|
handleToolError(
|
|
|
|
|
requestInfo.name,
|
|
|
|
|
toolResponse.error,
|
|
|
|
|
config,
|
|
|
|
|
toolResponse.errorType || 'TOOL_EXECUTION_ERROR',
|
|
|
|
|
typeof toolResponse.resultDisplay === 'string'
|
|
|
|
|
? toolResponse.resultDisplay
|
|
|
|
|
: undefined,
|
2025-09-09 01:14:15 -04:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (toolResponse.responseParts) {
|
|
|
|
|
toolResponseParts.push(...toolResponse.responseParts);
|
|
|
|
|
}
|
2025-06-01 16:11:37 -07:00
|
|
|
}
|
2025-09-09 01:14:15 -04:00
|
|
|
currentMessages = [{ role: 'user', parts: toolResponseParts }];
|
|
|
|
|
} else {
|
2025-09-11 05:19:47 +09:00
|
|
|
if (config.getOutputFormat() === OutputFormat.JSON) {
|
|
|
|
|
const formatter = new JsonFormatter();
|
|
|
|
|
const stats = uiTelemetryService.getMetrics();
|
|
|
|
|
process.stdout.write(formatter.format(responseText, stats));
|
|
|
|
|
} else {
|
|
|
|
|
process.stdout.write('\n'); // Ensure a final newline
|
|
|
|
|
}
|
2025-09-09 01:14:15 -04:00
|
|
|
return;
|
2025-06-01 16:11:37 -07:00
|
|
|
}
|
2025-09-09 01:14:15 -04:00
|
|
|
}
|
|
|
|
|
} catch (error) {
|
2025-09-11 05:19:47 +09:00
|
|
|
handleError(error, config);
|
2025-09-09 01:14:15 -04:00
|
|
|
} finally {
|
|
|
|
|
consolePatcher.cleanup();
|
|
|
|
|
if (isTelemetrySdkInitialized()) {
|
|
|
|
|
await shutdownTelemetry(config);
|
2025-06-01 16:11:37 -07:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-09 01:14:15 -04:00
|
|
|
});
|
2025-06-01 16:11:37 -07:00
|
|
|
}
|