mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-01-24 17:56:21 +00:00
197 lines
5.7 KiB
TypeScript
197 lines
5.7 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;
|
|
}
|
|
|
|
// Handle objects with message property (error-like objects)
|
|
if (
|
|
error !== null &&
|
|
typeof error === 'object' &&
|
|
'message' in error &&
|
|
typeof (error as { message: unknown }).message === 'string'
|
|
) {
|
|
return (error as { message: string }).message;
|
|
}
|
|
|
|
// Handle plain objects by stringifying them
|
|
if (error !== null && typeof error === 'object') {
|
|
try {
|
|
const stringified = JSON.stringify(error);
|
|
// JSON.stringify can return undefined for objects with toJSON() returning undefined
|
|
return stringified ?? String(error);
|
|
} catch {
|
|
// If JSON.stringify fails (circular reference, etc.), fall back to String
|
|
return String(error);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|