mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-01-18 23:06:19 +00:00
174 lines
5.1 KiB
TypeScript
174 lines
5.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import type { Config } from '@qwen-code/qwen-code-core';
|
|
import {
|
|
OutputFormat,
|
|
JsonFormatter,
|
|
parseAndFormatApiError,
|
|
FatalTurnLimitedError,
|
|
FatalCancellationError,
|
|
ToolErrorType,
|
|
} from '@qwen-code/qwen-code-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/STREAM_JSON mode, outputs error message to stderr only and does not exit.
|
|
* The error will be properly formatted in the tool_result block by the adapter,
|
|
* allowing the session to continue so the LLM can decide what to do next.
|
|
* In text mode, outputs error message to stderr only.
|
|
*
|
|
* @param toolName - Name of the tool that failed
|
|
* @param toolError - The error that occurred during tool execution
|
|
* @param config - Configuration object
|
|
* @param errorCode - Optional error code
|
|
* @param resultDisplay - Optional display message for the error
|
|
*/
|
|
export function handleToolError(
|
|
toolName: string,
|
|
toolError: Error,
|
|
config: Config,
|
|
errorCode?: string | number,
|
|
resultDisplay?: string,
|
|
): void {
|
|
// Check if this is a permission denied error in non-interactive mode
|
|
const isExecutionDenied = errorCode === ToolErrorType.EXECUTION_DENIED;
|
|
const isNonInteractive = !config.isInteractive();
|
|
const isTextMode = config.getOutputFormat() === OutputFormat.TEXT;
|
|
|
|
// Show warning for permission denied errors in non-interactive text mode
|
|
if (isExecutionDenied && isNonInteractive && isTextMode) {
|
|
const warningMessage =
|
|
`Warning: Tool "${toolName}" requires user approval but cannot execute in non-interactive mode.\n` +
|
|
`To enable automatic tool execution, use the -y flag (YOLO mode):\n` +
|
|
`Example: qwen -p 'your prompt' -y\n\n`;
|
|
process.stderr.write(warningMessage);
|
|
}
|
|
|
|
// Always log detailed error in debug mode
|
|
if (config.getDebugMode()) {
|
|
console.error(
|
|
`Error executing tool ${toolName}: ${resultDisplay || toolError.message}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|