mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-29 12:11:09 +00:00
Headless enhancement: add stream-json as input-format/output-format to support programmatically use (#926)
This commit is contained in:
parent
442a9aed58
commit
9e5387f159
50 changed files with 14559 additions and 534 deletions
|
|
@ -7,7 +7,6 @@
|
|||
import type {
|
||||
FileFilteringOptions,
|
||||
MCPServerConfig,
|
||||
OutputFormat,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { extensionsCommand } from '../commands/extensions.js';
|
||||
import {
|
||||
|
|
@ -24,6 +23,8 @@ import {
|
|||
WriteFileTool,
|
||||
resolveTelemetrySettings,
|
||||
FatalConfigError,
|
||||
InputFormat,
|
||||
OutputFormat,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import type { Settings } from './settings.js';
|
||||
import yargs, { type Argv } from 'yargs';
|
||||
|
|
@ -124,7 +125,24 @@ export interface CliArgs {
|
|||
screenReader: boolean | undefined;
|
||||
vlmSwitchMode: string | undefined;
|
||||
useSmartEdit: boolean | undefined;
|
||||
inputFormat?: string | undefined;
|
||||
outputFormat: string | undefined;
|
||||
includePartialMessages?: boolean;
|
||||
}
|
||||
|
||||
function normalizeOutputFormat(
|
||||
format: string | OutputFormat | undefined,
|
||||
): OutputFormat | undefined {
|
||||
if (!format) {
|
||||
return undefined;
|
||||
}
|
||||
if (format === OutputFormat.STREAM_JSON) {
|
||||
return OutputFormat.STREAM_JSON;
|
||||
}
|
||||
if (format === 'json' || format === OutputFormat.JSON) {
|
||||
return OutputFormat.JSON;
|
||||
}
|
||||
return OutputFormat.TEXT;
|
||||
}
|
||||
|
||||
export async function parseArguments(settings: Settings): Promise<CliArgs> {
|
||||
|
|
@ -359,11 +377,23 @@ export async function parseArguments(settings: Settings): Promise<CliArgs> {
|
|||
'Default behavior when images are detected in input. Values: once (one-time switch), session (switch for entire session), persist (continue with current model). Overrides settings files.',
|
||||
default: process.env['VLM_SWITCH_MODE'],
|
||||
})
|
||||
.option('input-format', {
|
||||
type: 'string',
|
||||
choices: ['text', 'stream-json'],
|
||||
description: 'The format consumed from standard input.',
|
||||
default: 'text',
|
||||
})
|
||||
.option('output-format', {
|
||||
alias: 'o',
|
||||
type: 'string',
|
||||
description: 'The format of the CLI output.',
|
||||
choices: ['text', 'json'],
|
||||
choices: ['text', 'json', 'stream-json'],
|
||||
})
|
||||
.option('include-partial-messages', {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Include partial assistant messages when using stream-json output.',
|
||||
default: false,
|
||||
})
|
||||
.deprecateOption(
|
||||
'show-memory-usage',
|
||||
|
|
@ -408,6 +438,18 @@ export async function parseArguments(settings: Settings): Promise<CliArgs> {
|
|||
if (argv['yolo'] && argv['approvalMode']) {
|
||||
return 'Cannot use both --yolo (-y) and --approval-mode together. Use --approval-mode=yolo instead.';
|
||||
}
|
||||
if (
|
||||
argv['includePartialMessages'] &&
|
||||
argv['outputFormat'] !== OutputFormat.STREAM_JSON
|
||||
) {
|
||||
return '--include-partial-messages requires --output-format stream-json';
|
||||
}
|
||||
if (
|
||||
argv['inputFormat'] === 'stream-json' &&
|
||||
argv['outputFormat'] !== OutputFormat.STREAM_JSON
|
||||
) {
|
||||
return '--input-format stream-json requires --output-format stream-json';
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
)
|
||||
|
|
@ -588,6 +630,22 @@ export async function loadCliConfig(
|
|||
|
||||
let mcpServers = mergeMcpServers(settings, activeExtensions);
|
||||
const question = argv.promptInteractive || argv.prompt || '';
|
||||
const inputFormat: InputFormat =
|
||||
(argv.inputFormat as InputFormat | undefined) ?? InputFormat.TEXT;
|
||||
const argvOutputFormat = normalizeOutputFormat(
|
||||
argv.outputFormat as string | OutputFormat | undefined,
|
||||
);
|
||||
const settingsOutputFormat = normalizeOutputFormat(settings.output?.format);
|
||||
const outputFormat =
|
||||
argvOutputFormat ?? settingsOutputFormat ?? OutputFormat.TEXT;
|
||||
const outputSettingsFormat: OutputFormat =
|
||||
outputFormat === OutputFormat.STREAM_JSON
|
||||
? settingsOutputFormat &&
|
||||
settingsOutputFormat !== OutputFormat.STREAM_JSON
|
||||
? settingsOutputFormat
|
||||
: OutputFormat.TEXT
|
||||
: (outputFormat as OutputFormat);
|
||||
const includePartialMessages = Boolean(argv.includePartialMessages);
|
||||
|
||||
// Determine approval mode with backward compatibility
|
||||
let approvalMode: ApprovalMode;
|
||||
|
|
@ -629,11 +687,31 @@ export async function loadCliConfig(
|
|||
throw err;
|
||||
}
|
||||
|
||||
// Interactive mode: explicit -i flag or (TTY + no args + no -p flag)
|
||||
// Interactive mode determination with priority:
|
||||
// 1. If promptInteractive (-i flag) is provided, it is explicitly interactive
|
||||
// 2. If outputFormat is stream-json or json (no matter input-format) along with query or prompt, it is non-interactive
|
||||
// 3. If no query or prompt is provided, check isTTY: TTY means interactive, non-TTY means non-interactive
|
||||
const hasQuery = !!argv.query;
|
||||
const interactive =
|
||||
!!argv.promptInteractive ||
|
||||
(process.stdin.isTTY && !hasQuery && !argv.prompt);
|
||||
const hasPrompt = !!argv.prompt;
|
||||
let interactive: boolean;
|
||||
if (argv.promptInteractive) {
|
||||
// Priority 1: Explicit -i flag means interactive
|
||||
interactive = true;
|
||||
} else if (
|
||||
(outputFormat === OutputFormat.STREAM_JSON ||
|
||||
outputFormat === OutputFormat.JSON) &&
|
||||
(hasQuery || hasPrompt)
|
||||
) {
|
||||
// Priority 2: JSON/stream-json output with query/prompt means non-interactive
|
||||
interactive = false;
|
||||
} else if (!hasQuery && !hasPrompt) {
|
||||
// Priority 3: No query or prompt means interactive only if TTY (format arguments ignored)
|
||||
interactive = process.stdin.isTTY ?? false;
|
||||
} else {
|
||||
// Default: If we have query/prompt but output format is TEXT, assume non-interactive
|
||||
// (fallback for edge cases where query/prompt is provided with TEXT output)
|
||||
interactive = false;
|
||||
}
|
||||
// In non-interactive mode, exclude tools that require a prompt.
|
||||
const extraExcludes: string[] = [];
|
||||
if (!interactive && !argv.experimentalAcp) {
|
||||
|
|
@ -755,6 +833,9 @@ export async function loadCliConfig(
|
|||
blockedMcpServers,
|
||||
noBrowser: !!process.env['NO_BROWSER'],
|
||||
authType: settings.security?.auth?.selectedType,
|
||||
inputFormat,
|
||||
outputFormat,
|
||||
includePartialMessages,
|
||||
generationConfig: {
|
||||
...(settings.model?.generationConfig || {}),
|
||||
model: resolvedModel,
|
||||
|
|
@ -798,7 +879,7 @@ export async function loadCliConfig(
|
|||
eventEmitter: appEvents,
|
||||
useSmartEdit: argv.useSmartEdit ?? settings.useSmartEdit,
|
||||
output: {
|
||||
format: (argv.outputFormat ?? settings.output?.format) as OutputFormat,
|
||||
format: outputSettingsFormat,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue