mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-05 10:51:12 -07:00
Structured JSON Output (#8119)
This commit is contained in:
@@ -4,9 +4,159 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import {
|
||||
OutputFormat,
|
||||
JsonFormatter,
|
||||
parseAndFormatApiError,
|
||||
FatalTurnLimitedError,
|
||||
FatalToolExecutionError,
|
||||
FatalCancellationError,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
export function getErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return String(error);
|
||||
}
|
||||
|
||||
interface ErrorWithCode extends Error {
|
||||
exitCode?: number;
|
||||
code?: string | number;
|
||||
status?: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the appropriate error code from an error object.
|
||||
*/
|
||||
function extractErrorCode(error: unknown): string | number {
|
||||
const errorWithCode = error as ErrorWithCode;
|
||||
|
||||
// Prioritize exitCode for FatalError types, fall back to other codes
|
||||
if (typeof errorWithCode.exitCode === 'number') {
|
||||
return errorWithCode.exitCode;
|
||||
}
|
||||
if (errorWithCode.code !== undefined) {
|
||||
return errorWithCode.code;
|
||||
}
|
||||
if (errorWithCode.status !== undefined) {
|
||||
return errorWithCode.status;
|
||||
}
|
||||
|
||||
return 1; // Default exit code
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an error code to a numeric exit code.
|
||||
*/
|
||||
function getNumericExitCode(errorCode: string | number): number {
|
||||
return typeof errorCode === 'number' ? errorCode : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors consistently for both JSON and text output formats.
|
||||
* In JSON mode, outputs formatted JSON error and exits.
|
||||
* In text mode, outputs error message and re-throws.
|
||||
*/
|
||||
export function handleError(
|
||||
error: unknown,
|
||||
config: Config,
|
||||
customErrorCode?: string | number,
|
||||
): never {
|
||||
const errorMessage = parseAndFormatApiError(
|
||||
error,
|
||||
config.getContentGeneratorConfig()?.authType,
|
||||
);
|
||||
|
||||
if (config.getOutputFormat() === OutputFormat.JSON) {
|
||||
const formatter = new JsonFormatter();
|
||||
const errorCode = customErrorCode ?? extractErrorCode(error);
|
||||
|
||||
const formattedError = formatter.formatError(
|
||||
error instanceof Error ? error : new Error(getErrorMessage(error)),
|
||||
errorCode,
|
||||
);
|
||||
|
||||
console.error(formattedError);
|
||||
process.exit(getNumericExitCode(errorCode));
|
||||
} else {
|
||||
console.error(errorMessage);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles tool execution errors specifically.
|
||||
* In JSON mode, outputs formatted JSON error and exits.
|
||||
* In text mode, outputs error message to stderr only.
|
||||
*/
|
||||
export function handleToolError(
|
||||
toolName: string,
|
||||
toolError: Error,
|
||||
config: Config,
|
||||
errorCode?: string | number,
|
||||
resultDisplay?: string,
|
||||
): void {
|
||||
const errorMessage = `Error executing tool ${toolName}: ${resultDisplay || toolError.message}`;
|
||||
const toolExecutionError = new FatalToolExecutionError(errorMessage);
|
||||
|
||||
if (config.getOutputFormat() === OutputFormat.JSON) {
|
||||
const formatter = new JsonFormatter();
|
||||
const formattedError = formatter.formatError(
|
||||
toolExecutionError,
|
||||
errorCode ?? toolExecutionError.exitCode,
|
||||
);
|
||||
|
||||
console.error(formattedError);
|
||||
process.exit(
|
||||
typeof errorCode === 'number' ? errorCode : toolExecutionError.exitCode,
|
||||
);
|
||||
} else {
|
||||
console.error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles cancellation/abort signals consistently.
|
||||
*/
|
||||
export function handleCancellationError(config: Config): never {
|
||||
const cancellationError = new FatalCancellationError('Operation cancelled.');
|
||||
|
||||
if (config.getOutputFormat() === OutputFormat.JSON) {
|
||||
const formatter = new JsonFormatter();
|
||||
const formattedError = formatter.formatError(
|
||||
cancellationError,
|
||||
cancellationError.exitCode,
|
||||
);
|
||||
|
||||
console.error(formattedError);
|
||||
process.exit(cancellationError.exitCode);
|
||||
} else {
|
||||
console.error(cancellationError.message);
|
||||
process.exit(cancellationError.exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles max session turns exceeded consistently.
|
||||
*/
|
||||
export function handleMaxTurnsExceededError(config: Config): never {
|
||||
const maxTurnsError = new FatalTurnLimitedError(
|
||||
'Reached max session turns for this session. Increase the number of turns by specifying maxSessionTurns in settings.json.',
|
||||
);
|
||||
|
||||
if (config.getOutputFormat() === OutputFormat.JSON) {
|
||||
const formatter = new JsonFormatter();
|
||||
const formattedError = formatter.formatError(
|
||||
maxTurnsError,
|
||||
maxTurnsError.exitCode,
|
||||
);
|
||||
|
||||
console.error(formattedError);
|
||||
process.exit(maxTurnsError.exitCode);
|
||||
} else {
|
||||
console.error(maxTurnsError.message);
|
||||
process.exit(maxTurnsError.exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user