refactor: streamline non-interactive session handling by removing settings parameter and introducing minimal settings instance

This commit is contained in:
mingholy.lmh
2025-11-18 12:20:47 +08:00
parent f9a2b7b032
commit 13cc8f9f55
6 changed files with 67 additions and 66 deletions

View File

@@ -484,6 +484,27 @@ export class LoadedSettings {
} }
} }
/**
* Creates a minimal LoadedSettings instance with empty settings.
* Used in stream-json mode where settings are ignored.
*/
export function createMinimalSettings(): LoadedSettings {
const emptySettingsFile: SettingsFile = {
path: '',
settings: {},
originalSettings: {},
rawJson: '{}',
};
return new LoadedSettings(
emptySettingsFile,
emptySettingsFile,
emptySettingsFile,
emptySettingsFile,
false,
new Set(),
);
}
function findEnvFile(startDir: string): string | null { function findEnvFile(startDir: string): string | null {
let currentDir = path.resolve(startDir); let currentDir = path.resolve(startDir);
while (true) { while (true) {

View File

@@ -357,12 +357,9 @@ describe('gemini.tsx main function', () => {
} }
expect(runStreamJsonSpy).toHaveBeenCalledTimes(1); expect(runStreamJsonSpy).toHaveBeenCalledTimes(1);
const [configArg, settingsArg, promptArg] = runStreamJsonSpy.mock.calls[0]; const [configArg, inputArg] = runStreamJsonSpy.mock.calls[0];
expect(configArg).toBe(validatedConfig); expect(configArg).toBe(validatedConfig);
expect(settingsArg).toMatchObject({ expect(inputArg).toBe('hello stream');
merged: expect.objectContaining({ security: expect.any(Object) }),
});
expect(promptArg).toBe('hello stream');
expect(validateAuthSpy).toHaveBeenCalledWith( expect(validateAuthSpy).toHaveBeenCalledWith(
undefined, undefined,

View File

@@ -446,9 +446,7 @@ export async function main() {
await runNonInteractiveStreamJson( await runNonInteractiveStreamJson(
nonInteractiveConfig, nonInteractiveConfig,
settings,
trimmedInput.length > 0 ? trimmedInput : '', trimmedInput.length > 0 ? trimmedInput : '',
prompt_id,
); );
await runExitCleanup(); await runExitCleanup();
process.exit(0); process.exit(0);

View File

@@ -6,7 +6,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { Config } from '@qwen-code/qwen-code-core'; import type { Config } from '@qwen-code/qwen-code-core';
import type { LoadedSettings } from '../config/settings.js';
import { runNonInteractiveStreamJson } from './session.js'; import { runNonInteractiveStreamJson } from './session.js';
import type { import type {
CLIUserMessage, CLIUserMessage,
@@ -74,14 +73,6 @@ function createConfig(overrides: ConfigOverrides = {}): Config {
return { ...base, ...overrides } as unknown as Config; return { ...base, ...overrides } as unknown as Config;
} }
function createSettings(): LoadedSettings {
return {
merged: {
security: { auth: {} },
},
} as unknown as LoadedSettings;
}
function createUserMessage(content: string): CLIUserMessage { function createUserMessage(content: string): CLIUserMessage {
return { return {
type: 'user', type: 'user',
@@ -145,7 +136,6 @@ function createControlCancel(requestId: string): ControlCancelRequest {
describe('runNonInteractiveStreamJson', () => { describe('runNonInteractiveStreamJson', () => {
let config: Config; let config: Config;
let settings: LoadedSettings;
let mockInputReader: { let mockInputReader: {
read: () => AsyncGenerator< read: () => AsyncGenerator<
| CLIUserMessage | CLIUserMessage
@@ -170,7 +160,6 @@ describe('runNonInteractiveStreamJson', () => {
beforeEach(() => { beforeEach(() => {
config = createConfig(); config = createConfig();
settings = createSettings();
runNonInteractiveMock.mockReset(); runNonInteractiveMock.mockReset();
// Setup mocks // Setup mocks
@@ -232,7 +221,7 @@ describe('runNonInteractiveStreamJson', () => {
yield initRequest; yield initRequest;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(mockConsolePatcher.patch).toHaveBeenCalledTimes(1); expect(mockConsolePatcher.patch).toHaveBeenCalledTimes(1);
expect(mockDispatcher.dispatch).toHaveBeenCalledWith(initRequest); expect(mockDispatcher.dispatch).toHaveBeenCalledWith(initRequest);
@@ -246,7 +235,7 @@ describe('runNonInteractiveStreamJson', () => {
yield userMessage; yield userMessage;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(runNonInteractiveMock).toHaveBeenCalledTimes(1); expect(runNonInteractiveMock).toHaveBeenCalledTimes(1);
const runCall = runNonInteractiveMock.mock.calls[0]; const runCall = runNonInteractiveMock.mock.calls[0];
@@ -272,7 +261,7 @@ describe('runNonInteractiveStreamJson', () => {
yield userMessage2; yield userMessage2;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(runNonInteractiveMock).toHaveBeenCalledTimes(2); expect(runNonInteractiveMock).toHaveBeenCalledTimes(2);
}); });
@@ -293,7 +282,7 @@ describe('runNonInteractiveStreamJson', () => {
yield userMessage2; yield userMessage2;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
// Both messages should be processed // Both messages should be processed
expect(runNonInteractiveMock).toHaveBeenCalledTimes(2); expect(runNonInteractiveMock).toHaveBeenCalledTimes(2);
@@ -308,7 +297,7 @@ describe('runNonInteractiveStreamJson', () => {
yield controlRequest; yield controlRequest;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(mockDispatcher.dispatch).toHaveBeenCalledTimes(2); expect(mockDispatcher.dispatch).toHaveBeenCalledTimes(2);
expect(mockDispatcher.dispatch).toHaveBeenNthCalledWith(1, initRequest); expect(mockDispatcher.dispatch).toHaveBeenNthCalledWith(1, initRequest);
@@ -324,7 +313,7 @@ describe('runNonInteractiveStreamJson', () => {
yield controlResponse; yield controlResponse;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(mockDispatcher.handleControlResponse).toHaveBeenCalledWith( expect(mockDispatcher.handleControlResponse).toHaveBeenCalledWith(
controlResponse, controlResponse,
@@ -340,7 +329,7 @@ describe('runNonInteractiveStreamJson', () => {
yield cancelRequest; yield cancelRequest;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(mockDispatcher.handleCancel).toHaveBeenCalledWith('req-2'); expect(mockDispatcher.handleCancel).toHaveBeenCalledWith('req-2');
}); });
@@ -360,7 +349,7 @@ describe('runNonInteractiveStreamJson', () => {
yield controlRequest; yield controlRequest;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(mockDispatcher.dispatch).toHaveBeenCalledWith(controlRequest); expect(mockDispatcher.dispatch).toHaveBeenCalledWith(controlRequest);
}); });
@@ -380,7 +369,7 @@ describe('runNonInteractiveStreamJson', () => {
yield controlResponse; yield controlResponse;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(mockDispatcher.handleControlResponse).toHaveBeenCalledWith( expect(mockDispatcher.handleControlResponse).toHaveBeenCalledWith(
controlResponse, controlResponse,
@@ -394,12 +383,12 @@ describe('runNonInteractiveStreamJson', () => {
yield userMessage; yield userMessage;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(runNonInteractiveMock).toHaveBeenCalledTimes(1); expect(runNonInteractiveMock).toHaveBeenCalledTimes(1);
expect(runNonInteractiveMock).toHaveBeenCalledWith( expect(runNonInteractiveMock).toHaveBeenCalledWith(
config, config,
settings, expect.objectContaining({ merged: expect.any(Object) }),
'Test message', 'Test message',
expect.stringContaining('test-session'), expect.stringContaining('test-session'),
expect.objectContaining({ expect.objectContaining({
@@ -427,12 +416,12 @@ describe('runNonInteractiveStreamJson', () => {
yield userMessage; yield userMessage;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(runNonInteractiveMock).toHaveBeenCalledTimes(1); expect(runNonInteractiveMock).toHaveBeenCalledTimes(1);
expect(runNonInteractiveMock).toHaveBeenCalledWith( expect(runNonInteractiveMock).toHaveBeenCalledWith(
config, config,
settings, expect.objectContaining({ merged: expect.any(Object) }),
'First part\nSecond part', 'First part\nSecond part',
expect.stringContaining('test-session'), expect.stringContaining('test-session'),
expect.objectContaining({ expect.objectContaining({
@@ -457,7 +446,7 @@ describe('runNonInteractiveStreamJson', () => {
yield userMessage; yield userMessage;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(runNonInteractiveMock).not.toHaveBeenCalled(); expect(runNonInteractiveMock).not.toHaveBeenCalled();
}); });
@@ -472,7 +461,7 @@ describe('runNonInteractiveStreamJson', () => {
yield userMessage; yield userMessage;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
// Error should be caught and handled gracefully // Error should be caught and handled gracefully
}); });
@@ -484,9 +473,9 @@ describe('runNonInteractiveStreamJson', () => {
throw streamError; throw streamError;
} as typeof mockInputReader.read; } as typeof mockInputReader.read;
await expect( await expect(runNonInteractiveStreamJson(config, '')).rejects.toThrow(
runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'), 'Stream error',
).rejects.toThrow('Stream error'); );
expect(mockConsolePatcher.cleanup).toHaveBeenCalled(); expect(mockConsolePatcher.cleanup).toHaveBeenCalled();
}); });
@@ -517,7 +506,7 @@ describe('runNonInteractiveStreamJson', () => {
yield userMessage; yield userMessage;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
// Verify initialization happened // Verify initialization happened
expect(mockDispatcher.dispatch).toHaveBeenCalledWith(initRequest); expect(mockDispatcher.dispatch).toHaveBeenCalledWith(initRequest);
@@ -536,7 +525,7 @@ describe('runNonInteractiveStreamJson', () => {
yield userMessage2; yield userMessage2;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(runNonInteractiveMock).toHaveBeenCalledTimes(2); expect(runNonInteractiveMock).toHaveBeenCalledTimes(2);
const promptId1 = runNonInteractiveMock.mock.calls[0][3] as string; const promptId1 = runNonInteractiveMock.mock.calls[0][3] as string;
@@ -553,7 +542,7 @@ describe('runNonInteractiveStreamJson', () => {
yield controlRequest; yield controlRequest;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
// Should not transition to idle since it's not an initialize request // Should not transition to idle since it's not an initialize request
expect(mockDispatcher.dispatch).not.toHaveBeenCalled(); expect(mockDispatcher.dispatch).not.toHaveBeenCalled();
@@ -564,7 +553,7 @@ describe('runNonInteractiveStreamJson', () => {
// Empty stream - should complete immediately // Empty stream - should complete immediately
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(mockConsolePatcher.patch).toHaveBeenCalledTimes(1); expect(mockConsolePatcher.patch).toHaveBeenCalledTimes(1);
expect(mockConsolePatcher.cleanup).toHaveBeenCalledTimes(1); expect(mockConsolePatcher.cleanup).toHaveBeenCalledTimes(1);
@@ -575,7 +564,7 @@ describe('runNonInteractiveStreamJson', () => {
// Empty stream // Empty stream
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
}); });
it('calls dispatcher shutdown on completion', async () => { it('calls dispatcher shutdown on completion', async () => {
@@ -585,7 +574,7 @@ describe('runNonInteractiveStreamJson', () => {
yield initRequest; yield initRequest;
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(mockDispatcher.shutdown).toHaveBeenCalledTimes(1); expect(mockDispatcher.shutdown).toHaveBeenCalledTimes(1);
}); });
@@ -595,7 +584,7 @@ describe('runNonInteractiveStreamJson', () => {
// Empty stream // Empty stream
}; };
await runNonInteractiveStreamJson(config, settings, '', 'test-prompt-id'); await runNonInteractiveStreamJson(config, '');
expect(mockConsolePatcher.cleanup).toHaveBeenCalled(); expect(mockConsolePatcher.cleanup).toHaveBeenCalled();
}); });

View File

@@ -38,7 +38,7 @@ import {
isControlResponse, isControlResponse,
isControlCancel, isControlCancel,
} from './types.js'; } from './types.js';
import type { LoadedSettings } from '../config/settings.js'; import { createMinimalSettings } from '../config/settings.js';
import { runNonInteractive } from '../nonInteractiveCli.js'; import { runNonInteractive } from '../nonInteractiveCli.js';
import { ConsolePatcher } from '../ui/utils/ConsolePatcher.js'; import { ConsolePatcher } from '../ui/utils/ConsolePatcher.js';
@@ -87,7 +87,6 @@ class SessionManager {
private userMessageQueue: CLIUserMessage[] = []; private userMessageQueue: CLIUserMessage[] = [];
private abortController: AbortController; private abortController: AbortController;
private config: Config; private config: Config;
private settings: LoadedSettings;
private sessionId: string; private sessionId: string;
private promptIdCounter: number = 0; private promptIdCounter: number = 0;
private inputReader: StreamJsonInputReader; private inputReader: StreamJsonInputReader;
@@ -100,13 +99,8 @@ class SessionManager {
private shutdownHandler: (() => void) | null = null; private shutdownHandler: (() => void) | null = null;
private initialPrompt: CLIUserMessage | null = null; private initialPrompt: CLIUserMessage | null = null;
constructor( constructor(config: Config, initialPrompt?: CLIUserMessage) {
config: Config,
settings: LoadedSettings,
initialPrompt?: CLIUserMessage,
) {
this.config = config; this.config = config;
this.settings = settings;
this.sessionId = config.getSessionId(); this.sessionId = config.getSessionId();
this.debugMode = config.getDebugMode(); this.debugMode = config.getDebugMode();
this.abortController = new AbortController(); this.abortController = new AbortController();
@@ -569,11 +563,17 @@ class SessionManager {
const promptId = this.getNextPromptId(); const promptId = this.getNextPromptId();
try { try {
await runNonInteractive(this.config, this.settings, input, promptId, { await runNonInteractive(
abortController: this.abortController, this.config,
adapter: this.outputAdapter, createMinimalSettings(),
controlService: this.controlService ?? undefined, input,
}); promptId,
{
abortController: this.abortController,
adapter: this.outputAdapter,
controlService: this.controlService ?? undefined,
},
);
} catch (error) { } catch (error) {
// Error already handled by runNonInteractive via adapter.emitResult // Error already handled by runNonInteractive via adapter.emitResult
if (this.debugMode) { if (this.debugMode) {
@@ -686,15 +686,11 @@ function extractUserMessageText(message: CLIUserMessage): string | null {
* Entry point for stream-json mode * Entry point for stream-json mode
* *
* @param config - Configuration object * @param config - Configuration object
* @param settings - Loaded settings
* @param input - Optional initial prompt input to process before reading from stream * @param input - Optional initial prompt input to process before reading from stream
* @param promptId - Prompt ID (not used in stream-json mode but kept for API compatibility)
*/ */
export async function runNonInteractiveStreamJson( export async function runNonInteractiveStreamJson(
config: Config, config: Config,
settings: LoadedSettings,
input: string, input: string,
_promptId: string,
): Promise<void> { ): Promise<void> {
const consolePatcher = new ConsolePatcher({ const consolePatcher = new ConsolePatcher({
debugMode: config.getDebugMode(), debugMode: config.getDebugMode(),
@@ -717,7 +713,7 @@ export async function runNonInteractiveStreamJson(
}; };
} }
const manager = new SessionManager(config, settings, initialPrompt); const manager = new SessionManager(config, initialPrompt);
await manager.run(); await manager.run();
} finally { } finally {
consolePatcher.cleanup(); consolePatcher.cleanup();