Merge branch 'main' into feat/debug-logging-refactor

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
tanzhenxin 2026-02-01 20:47:38 +08:00
commit 135df54f27
378 changed files with 40051 additions and 6776 deletions

View file

@ -4,11 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type {
Config,
ToolCallRequestInfo,
ToolResultDisplay,
} from '@qwen-code/qwen-code-core';
import type { Config, ToolCallRequestInfo } from '@qwen-code/qwen-code-core';
import { isSlashCommand } from './ui/utils/commandUtils.js';
import type { LoadedSettings } from './config/settings.js';
import {
@ -56,19 +52,12 @@ import {
async function emitNonInteractiveFinalMessage(params: {
message: string;
isError: boolean;
adapter?: JsonOutputAdapterInterface;
adapter: JsonOutputAdapterInterface;
config: Config;
startTimeMs: number;
}): Promise<void> {
const { message, isError, adapter, config } = params;
if (!adapter) {
// Text output mode: write directly to stdout/stderr
const target = isError ? process.stderr : process.stdout;
target.write(`${message}\n`);
return;
}
// JSON output mode: emit assistant message and result
// (systemMessage should already be emitted by caller)
adapter.startAssistantMessage();
@ -125,18 +114,18 @@ export async function runNonInteractive(
): Promise<void> {
return promptIdContext.run(prompt_id, async () => {
// Create output adapter based on format
let adapter: JsonOutputAdapterInterface | undefined;
let adapter: JsonOutputAdapterInterface;
const outputFormat = config.getOutputFormat();
if (options.adapter) {
adapter = options.adapter;
} else if (outputFormat === OutputFormat.JSON) {
adapter = new JsonOutputAdapter(config);
} else if (outputFormat === OutputFormat.STREAM_JSON) {
adapter = new StreamJsonOutputAdapter(
config,
config.getIncludePartialMessages(),
);
} else {
adapter = new JsonOutputAdapter(config);
}
// Get readonly values once at the start
@ -170,14 +159,12 @@ export async function runNonInteractive(
process.on('SIGTERM', shutdownHandler);
// Emit systemMessage first (always the first message in JSON mode)
if (adapter) {
const systemMessage = await buildSystemMessage(
config,
sessionId,
permissionMode,
);
adapter.emitMessage(systemMessage);
}
const systemMessage = await buildSystemMessage(
config,
sessionId,
permissionMode,
);
adapter.emitMessage(systemMessage);
let initialPartList: PartListUnion | null = extractPartsFromUserMessage(
options.userMessage,
@ -283,46 +270,33 @@ export async function runNonInteractive(
isFirstTurn = false;
// Start assistant message for this turn
if (adapter) {
adapter.startAssistantMessage();
}
adapter.startAssistantMessage();
for await (const event of responseStream) {
if (abortController.signal.aborted) {
handleCancellationError(config);
}
if (adapter) {
// Use adapter for all event processing
adapter.processEvent(event);
if (event.type === GeminiEventType.ToolCallRequest) {
toolCallRequests.push(event.value);
}
} else {
// Text output mode - direct stdout
if (event.type === GeminiEventType.Thought) {
process.stdout.write(event.value.description);
} else if (event.type === GeminiEventType.Content) {
process.stdout.write(event.value);
} else if (event.type === GeminiEventType.ToolCallRequest) {
toolCallRequests.push(event.value);
} else if (event.type === GeminiEventType.Error) {
// Format and output the error message for text mode
const errorText = parseAndFormatApiError(
event.value.error,
config.getContentGeneratorConfig()?.authType,
);
process.stderr.write(`${errorText}\n`);
// Throw error to exit with non-zero code
throw new Error(errorText);
}
// Use adapter for all event processing
adapter.processEvent(event);
if (event.type === GeminiEventType.ToolCallRequest) {
toolCallRequests.push(event.value);
}
if (
outputFormat === OutputFormat.TEXT &&
event.type === GeminiEventType.Error
) {
const errorText = parseAndFormatApiError(
event.value.error,
config.getContentGeneratorConfig()?.authType,
);
process.stderr.write(`${errorText}\n`);
// Throw error to exit with non-zero code
throw new Error(errorText);
}
}
// Finalize assistant message
if (adapter) {
adapter.finalizeAssistantMessage();
}
adapter.finalizeAssistantMessage();
totalApiDurationMs += Date.now() - apiStartTime;
if (toolCallRequests.length > 0) {
@ -351,35 +325,13 @@ export async function runNonInteractive(
: undefined;
const taskToolProgressHandler = taskToolProgress?.handler;
// Create output handler for non-Task tools in text mode (for console output)
const nonTaskOutputHandler =
!isTaskTool && !adapter
? (callId: string, outputChunk: ToolResultDisplay) => {
// Print tool output to console in text mode
if (typeof outputChunk === 'string') {
process.stdout.write(outputChunk);
} else if (
outputChunk &&
typeof outputChunk === 'object' &&
'ansiOutput' in outputChunk
) {
// Handle ANSI output - just print as string for now
process.stdout.write(String(outputChunk.ansiOutput));
}
}
: undefined;
// Combine output handlers
const outputUpdateHandler =
taskToolProgressHandler || nonTaskOutputHandler;
const toolResponse = await executeToolCall(
config,
finalRequestInfo,
abortController.signal,
outputUpdateHandler || toolCallUpdateCallback
taskToolProgressHandler || toolCallUpdateCallback
? {
...(outputUpdateHandler && { outputUpdateHandler }),
...(taskToolProgressHandler && { taskToolProgressHandler }),
...(toolCallUpdateCallback && {
onToolCallsUpdate: toolCallUpdateCallback,
}),
@ -406,9 +358,7 @@ export async function runNonInteractive(
);
}
if (adapter) {
adapter.emitToolResult(finalRequestInfo, toolResponse);
}
adapter.emitToolResult(finalRequestInfo, toolResponse);
if (toolResponse.responseParts) {
toolResponseParts.push(...toolResponse.responseParts);
@ -416,51 +366,43 @@ export async function runNonInteractive(
}
currentMessages = [{ role: 'user', parts: toolResponseParts }];
} else {
// For JSON and STREAM_JSON modes, compute usage from metrics
if (adapter) {
const metrics = uiTelemetryService.getMetrics();
const usage = computeUsageFromMetrics(metrics);
// Get stats for JSON format output
const stats =
outputFormat === OutputFormat.JSON
? uiTelemetryService.getMetrics()
: undefined;
adapter.emitResult({
isError: false,
durationMs: Date.now() - startTime,
apiDurationMs: totalApiDurationMs,
numTurns: turnCount,
usage,
stats,
});
} else {
// Text output mode - no usage needed
process.stdout.write('\n');
}
const metrics = uiTelemetryService.getMetrics();
const usage = computeUsageFromMetrics(metrics);
// Get stats for JSON format output
const stats =
outputFormat === OutputFormat.JSON
? uiTelemetryService.getMetrics()
: undefined;
adapter.emitResult({
isError: false,
durationMs: Date.now() - startTime,
apiDurationMs: totalApiDurationMs,
numTurns: turnCount,
usage,
stats,
});
return;
}
}
} catch (error) {
// For JSON and STREAM_JSON modes, compute usage from metrics
const message = error instanceof Error ? error.message : String(error);
if (adapter) {
const metrics = uiTelemetryService.getMetrics();
const usage = computeUsageFromMetrics(metrics);
// Get stats for JSON format output
const stats =
outputFormat === OutputFormat.JSON
? uiTelemetryService.getMetrics()
: undefined;
adapter.emitResult({
isError: true,
durationMs: Date.now() - startTime,
apiDurationMs: totalApiDurationMs,
numTurns: turnCount,
errorMessage: message,
usage,
stats,
});
}
const metrics = uiTelemetryService.getMetrics();
const usage = computeUsageFromMetrics(metrics);
// Get stats for JSON format output
const stats =
outputFormat === OutputFormat.JSON
? uiTelemetryService.getMetrics()
: undefined;
adapter.emitResult({
isError: true,
durationMs: Date.now() - startTime,
apiDurationMs: totalApiDurationMs,
numTurns: turnCount,
errorMessage: message,
usage,
stats,
});
handleError(error, config);
} finally {
process.stdout.removeListener('error', stdoutErrorHandler);