Merge branch 'main' into web-search

This commit is contained in:
pomelo-nwu
2025-10-30 19:42:00 +08:00
73 changed files with 1367 additions and 1661 deletions

View File

@@ -18,60 +18,26 @@ vi.mock('./settings.js', () => ({
describe('validateAuthMethod', () => {
beforeEach(() => {
vi.resetModules();
vi.stubEnv('GEMINI_API_KEY', undefined);
vi.stubEnv('GOOGLE_CLOUD_PROJECT', undefined);
vi.stubEnv('GOOGLE_CLOUD_LOCATION', undefined);
vi.stubEnv('GOOGLE_API_KEY', undefined);
});
afterEach(() => {
vi.unstubAllEnvs();
});
it('should return null for LOGIN_WITH_GOOGLE', () => {
expect(validateAuthMethod(AuthType.LOGIN_WITH_GOOGLE)).toBeNull();
it('should return null for USE_OPENAI', () => {
process.env['OPENAI_API_KEY'] = 'fake-key';
expect(validateAuthMethod(AuthType.USE_OPENAI)).toBeNull();
});
it('should return null for CLOUD_SHELL', () => {
expect(validateAuthMethod(AuthType.CLOUD_SHELL)).toBeNull();
it('should return an error message for USE_OPENAI if OPENAI_API_KEY is not set', () => {
delete process.env['OPENAI_API_KEY'];
expect(validateAuthMethod(AuthType.USE_OPENAI)).toBe(
'OPENAI_API_KEY environment variable not found. You can enter it interactively or add it to your .env file.',
);
});
describe('USE_GEMINI', () => {
it('should return null if GEMINI_API_KEY is set', () => {
vi.stubEnv('GEMINI_API_KEY', 'test-key');
expect(validateAuthMethod(AuthType.USE_GEMINI)).toBeNull();
});
it('should return an error message if GEMINI_API_KEY is not set', () => {
vi.stubEnv('GEMINI_API_KEY', undefined);
expect(validateAuthMethod(AuthType.USE_GEMINI)).toBe(
'GEMINI_API_KEY environment variable not found. Add that to your environment and try again (no reload needed if using .env)!',
);
});
});
describe('USE_VERTEX_AI', () => {
it('should return null if GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION are set', () => {
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'test-project');
vi.stubEnv('GOOGLE_CLOUD_LOCATION', 'test-location');
expect(validateAuthMethod(AuthType.USE_VERTEX_AI)).toBeNull();
});
it('should return null if GOOGLE_API_KEY is set', () => {
vi.stubEnv('GOOGLE_API_KEY', 'test-api-key');
expect(validateAuthMethod(AuthType.USE_VERTEX_AI)).toBeNull();
});
it('should return an error message if no required environment variables are set', () => {
vi.stubEnv('GOOGLE_CLOUD_PROJECT', undefined);
vi.stubEnv('GOOGLE_CLOUD_LOCATION', undefined);
expect(validateAuthMethod(AuthType.USE_VERTEX_AI)).toBe(
'When using Vertex AI, you must specify either:\n' +
'• GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION environment variables.\n' +
'• GOOGLE_API_KEY environment variable (if using express mode).\n' +
'Update your environment and try again (no reload needed if using .env)!',
);
});
it('should return null for QWEN_OAUTH', () => {
expect(validateAuthMethod(AuthType.QWEN_OAUTH)).toBeNull();
});
it('should return an error message for an invalid auth method', () => {

View File

@@ -8,39 +8,13 @@ import { AuthType } from '@qwen-code/qwen-code-core';
import { loadEnvironment, loadSettings } from './settings.js';
export function validateAuthMethod(authMethod: string): string | null {
loadEnvironment(loadSettings().merged);
if (
authMethod === AuthType.LOGIN_WITH_GOOGLE ||
authMethod === AuthType.CLOUD_SHELL
) {
return null;
}
if (authMethod === AuthType.USE_GEMINI) {
if (!process.env['GEMINI_API_KEY']) {
return 'GEMINI_API_KEY environment variable not found. Add that to your environment and try again (no reload needed if using .env)!';
}
return null;
}
if (authMethod === AuthType.USE_VERTEX_AI) {
const hasVertexProjectLocationConfig =
!!process.env['GOOGLE_CLOUD_PROJECT'] &&
!!process.env['GOOGLE_CLOUD_LOCATION'];
const hasGoogleApiKey = !!process.env['GOOGLE_API_KEY'];
if (!hasVertexProjectLocationConfig && !hasGoogleApiKey) {
return (
'When using Vertex AI, you must specify either:\n' +
'• GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION environment variables.\n' +
'• GOOGLE_API_KEY environment variable (if using express mode).\n' +
'Update your environment and try again (no reload needed if using .env)!'
);
}
return null;
}
const settings = loadSettings();
loadEnvironment(settings.merged);
if (authMethod === AuthType.USE_OPENAI) {
if (!process.env['OPENAI_API_KEY']) {
const hasApiKey =
process.env['OPENAI_API_KEY'] || settings.merged.security?.auth?.apiKey;
if (!hasApiKey) {
return 'OPENAI_API_KEY environment variable not found. You can enter it interactively or add it to your .env file.';
}
return null;
@@ -54,15 +28,3 @@ export function validateAuthMethod(authMethod: string): string | null {
return 'Invalid auth method selected.';
}
export const setOpenAIApiKey = (apiKey: string): void => {
process.env['OPENAI_API_KEY'] = apiKey;
};
export const setOpenAIBaseUrl = (baseUrl: string): void => {
process.env['OPENAI_BASE_URL'] = baseUrl;
};
export const setOpenAIModel = (model: string): void => {
process.env['OPENAI_MODEL'] = model;
};

View File

@@ -2051,7 +2051,7 @@ describe('loadCliConfig extensions', () => {
});
describe('loadCliConfig model selection', () => {
it('selects a model from settings.json if provided', async () => {
it.skip('selects a model from settings.json if provided', async () => {
process.argv = ['node', 'script.js'];
const argv = await parseArguments({} as Settings);
const config = await loadCliConfig(
@@ -2072,7 +2072,7 @@ describe('loadCliConfig model selection', () => {
expect(config.getModel()).toBe('qwen3-coder-plus');
});
it('uses the default gemini model if nothing is set', async () => {
it.skip('uses the default gemini model if nothing is set', async () => {
process.argv = ['node', 'script.js']; // No model set.
const argv = await parseArguments({} as Settings);
const config = await loadCliConfig(

View File

@@ -13,7 +13,6 @@ import { extensionsCommand } from '../commands/extensions.js';
import {
ApprovalMode,
Config,
DEFAULT_QWEN_MODEL,
DEFAULT_QWEN_EMBEDDING_MODEL,
DEFAULT_MEMORY_FILE_FILTERING_OPTIONS,
EditTool,
@@ -199,7 +198,7 @@ export async function parseArguments(settings: Settings): Promise<CliArgs> {
.option('proxy', {
type: 'string',
description:
'Proxy for gemini client, like schema://user:password@host:port',
'Proxy for Qwen Code, like schema://user:password@host:port',
})
.deprecateOption(
'proxy',
@@ -686,13 +685,11 @@ export async function loadCliConfig(
);
}
const defaultModel = DEFAULT_QWEN_MODEL;
const resolvedModel: string =
const resolvedModel =
argv.model ||
process.env['OPENAI_MODEL'] ||
process.env['QWEN_MODEL'] ||
settings.model?.name ||
defaultModel;
settings.model?.name;
const sandboxConfig = await loadSandboxConfig(settings, argv);
const screenReader =
@@ -756,8 +753,14 @@ export async function loadCliConfig(
generationConfig: {
...(settings.model?.generationConfig || {}),
model: resolvedModel,
apiKey: argv.openaiApiKey || process.env['OPENAI_API_KEY'],
baseUrl: argv.openaiBaseUrl || process.env['OPENAI_BASE_URL'],
apiKey:
argv.openaiApiKey ||
process.env['OPENAI_API_KEY'] ||
settings.security?.auth?.apiKey,
baseUrl:
argv.openaiBaseUrl ||
process.env['OPENAI_BASE_URL'] ||
settings.security?.auth?.baseUrl,
enableOpenAILogging:
(typeof argv.openaiLogging === 'undefined'
? settings.model?.enableOpenAILogging

View File

@@ -991,6 +991,24 @@ const SETTINGS_SCHEMA = {
description: 'Whether to use an external authentication flow.',
showInDialog: false,
},
apiKey: {
type: 'string',
label: 'API Key',
category: 'Security',
requiresRestart: true,
default: undefined as string | undefined,
description: 'API key for OpenAI compatible authentication.',
showInDialog: false,
},
baseUrl: {
type: 'string',
label: 'Base URL',
category: 'Security',
requiresRestart: true,
default: undefined as string | undefined,
description: 'Base URL for OpenAI compatible API.',
showInDialog: false,
},
},
},
},