mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 19:52:02 +00:00
feat: support /compress and /summary commands for non-interactive & ACP
integration
This commit is contained in:
parent
cebe0448d0
commit
8aceddffa2
12 changed files with 720 additions and 147 deletions
|
|
@ -42,6 +42,61 @@ import {
|
|||
computeUsageFromMetrics,
|
||||
} from './utils/nonInteractiveHelpers.js';
|
||||
|
||||
const ALLOWED_BUILTIN_COMMANDS_FOR_NON_INTERACTIVE = [
|
||||
'init',
|
||||
'summary',
|
||||
'compress',
|
||||
];
|
||||
|
||||
/**
|
||||
* Emits a final message for slash command results.
|
||||
* Note: systemMessage should already be emitted before calling this function.
|
||||
*/
|
||||
async function emitNonInteractiveFinalMessage(params: {
|
||||
message: string;
|
||||
isError: boolean;
|
||||
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();
|
||||
adapter.processEvent({
|
||||
type: GeminiEventType.Content,
|
||||
value: message,
|
||||
} as unknown as Parameters<JsonOutputAdapterInterface['processEvent']>[0]);
|
||||
adapter.finalizeAssistantMessage();
|
||||
|
||||
const metrics = uiTelemetryService.getMetrics();
|
||||
const usage = computeUsageFromMetrics(metrics);
|
||||
const outputFormat = config.getOutputFormat();
|
||||
const stats =
|
||||
outputFormat === OutputFormat.JSON
|
||||
? uiTelemetryService.getMetrics()
|
||||
: undefined;
|
||||
|
||||
adapter.emitResult({
|
||||
isError,
|
||||
durationMs: Date.now() - params.startTimeMs,
|
||||
apiDurationMs: 0,
|
||||
numTurns: 0,
|
||||
errorMessage: isError ? message : undefined,
|
||||
usage,
|
||||
stats,
|
||||
summary: message,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides optional overrides for `runNonInteractive` execution.
|
||||
*
|
||||
|
|
@ -115,6 +170,16 @@ export async function runNonInteractive(
|
|||
process.on('SIGINT', shutdownHandler);
|
||||
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);
|
||||
}
|
||||
|
||||
let initialPartList: PartListUnion | null = extractPartsFromUserMessage(
|
||||
options.userMessage,
|
||||
);
|
||||
|
|
@ -127,11 +192,41 @@ export async function runNonInteractive(
|
|||
abortController,
|
||||
config,
|
||||
settings,
|
||||
ALLOWED_BUILTIN_COMMANDS_FOR_NON_INTERACTIVE,
|
||||
);
|
||||
if (slashCommandResult) {
|
||||
// A slash command can replace the prompt entirely; fall back to @-command processing otherwise.
|
||||
initialPartList = slashCommandResult as PartListUnion;
|
||||
slashHandled = true;
|
||||
switch (slashCommandResult.type) {
|
||||
case 'submit_prompt':
|
||||
// A slash command can replace the prompt entirely; fall back to @-command processing otherwise.
|
||||
initialPartList = slashCommandResult.content;
|
||||
slashHandled = true;
|
||||
break;
|
||||
case 'message': {
|
||||
// systemMessage already emitted above
|
||||
await emitNonInteractiveFinalMessage({
|
||||
message: slashCommandResult.content,
|
||||
isError: slashCommandResult.messageType === 'error',
|
||||
adapter,
|
||||
config,
|
||||
startTimeMs: startTime,
|
||||
});
|
||||
return;
|
||||
}
|
||||
case 'stream_messages':
|
||||
// ACP exclusive - should not reach here in non-interactive mode
|
||||
throw new FatalInputError(
|
||||
'Stream messages mode is not supported in non-interactive CLI',
|
||||
);
|
||||
break;
|
||||
case 'unsupported':
|
||||
throw new FatalInputError(slashCommandResult.reason);
|
||||
case 'no_command':
|
||||
break;
|
||||
default: {
|
||||
const _exhaustive: never = slashCommandResult;
|
||||
throw new FatalInputError(
|
||||
`Unhandled slash command result type: ${(_exhaustive as { type: string }).type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,15 +258,6 @@ export async function runNonInteractive(
|
|||
const initialParts = normalizePartList(initialPartList);
|
||||
let currentMessages: Content[] = [{ role: 'user', parts: initialParts }];
|
||||
|
||||
if (adapter) {
|
||||
const systemMessage = await buildSystemMessage(
|
||||
config,
|
||||
sessionId,
|
||||
permissionMode,
|
||||
);
|
||||
adapter.emitMessage(systemMessage);
|
||||
}
|
||||
|
||||
let isFirstTurn = true;
|
||||
while (true) {
|
||||
turnCount++;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue