mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-29 12:11:09 +00:00
fix(cli): honor --openai-api-key in non-interactive auth validation (#3187)
validateAuthMethod's pre-flight check only inspected OPENAI_API_KEY (and settings.security.auth.apiKey), so credentials supplied via --openai-api-key were rejected even though refreshAuth would have accepted them. macOS users were unaffected because OPENAI_API_KEY is commonly exported in their shell profile; on Linux without that env var, the CLI failed to start. hasApiKeyForAuth now prefers the API key already resolved into generationConfig.apiKey when a Config is provided. The unified resolver folds CLI flags, env vars, settings, and modelProvider envKey lookups into this single value, so validation matches runtime behavior. Fixes #3171
This commit is contained in:
parent
9cdf7bd7c8
commit
62867702f7
3 changed files with 77 additions and 0 deletions
|
|
@ -186,6 +186,7 @@ describe('validateAuthMethod', () => {
|
|||
const mockConfig = {
|
||||
getModelsConfig: vi.fn().mockReturnValue({
|
||||
getModel: vi.fn().mockReturnValue('cli-model'),
|
||||
getGenerationConfig: vi.fn().mockReturnValue({}),
|
||||
}),
|
||||
} as unknown as import('@qwen-code/qwen-code-core').Config;
|
||||
|
||||
|
|
@ -219,6 +220,7 @@ describe('validateAuthMethod', () => {
|
|||
const mockConfig = {
|
||||
getModelsConfig: vi.fn().mockReturnValue({
|
||||
getModel: vi.fn().mockReturnValue('cli-model'),
|
||||
getGenerationConfig: vi.fn().mockReturnValue({}),
|
||||
}),
|
||||
} as unknown as import('@qwen-code/qwen-code-core').Config;
|
||||
|
||||
|
|
@ -227,4 +229,54 @@ describe('validateAuthMethod', () => {
|
|||
expect(result).not.toBeNull();
|
||||
expect(result).toContain('CLI_API_KEY');
|
||||
});
|
||||
|
||||
// Regression test for #3171: validation must accept the API key resolved
|
||||
// into generationConfig.apiKey (e.g. from --openai-api-key) instead of
|
||||
// requiring an OPENAI_API_KEY env var.
|
||||
it('should accept API key resolved into generationConfig from CLI flag', () => {
|
||||
delete process.env['OPENAI_API_KEY'];
|
||||
vi.mocked(settings.loadSettings).mockReturnValue({
|
||||
merged: {},
|
||||
} as unknown as ReturnType<typeof settings.loadSettings>);
|
||||
|
||||
const mockConfig = {
|
||||
getModelsConfig: vi.fn().mockReturnValue({
|
||||
getModel: vi.fn().mockReturnValue('gpt-4'),
|
||||
getGenerationConfig: vi
|
||||
.fn()
|
||||
.mockReturnValue({ apiKey: 'cli-provided-key' }),
|
||||
}),
|
||||
} as unknown as import('@qwen-code/qwen-code-core').Config;
|
||||
|
||||
const result = validateAuthMethod(AuthType.USE_OPENAI, mockConfig);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
// Regression test for #3171: when a modelProvider has a custom envKey but
|
||||
// the user passes --openai-api-key on the CLI, the resolver picks the CLI
|
||||
// value. Validation should match the resolver and accept it instead of
|
||||
// demanding the env var.
|
||||
it('should accept CLI-resolved key even when modelProvider declares a custom envKey', () => {
|
||||
delete process.env['CUSTOM_API_KEY'];
|
||||
vi.mocked(settings.loadSettings).mockReturnValue({
|
||||
merged: {
|
||||
model: { name: 'custom-model' },
|
||||
modelProviders: {
|
||||
openai: [{ id: 'custom-model', envKey: 'CUSTOM_API_KEY' }],
|
||||
},
|
||||
},
|
||||
} as unknown as ReturnType<typeof settings.loadSettings>);
|
||||
|
||||
const mockConfig = {
|
||||
getModelsConfig: vi.fn().mockReturnValue({
|
||||
getModel: vi.fn().mockReturnValue('custom-model'),
|
||||
getGenerationConfig: vi
|
||||
.fn()
|
||||
.mockReturnValue({ apiKey: 'cli-provided-key' }),
|
||||
}),
|
||||
} as unknown as import('@qwen-code/qwen-code-core').Config;
|
||||
|
||||
const result = validateAuthMethod(AuthType.USE_OPENAI, mockConfig);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -66,6 +66,26 @@ function hasApiKeyForAuth(
|
|||
|
||||
// Try to find model-specific envKey from modelProviders
|
||||
const modelConfig = findModelConfig(modelProviders, authType, modelId);
|
||||
|
||||
// If a Config is available, prefer the API key already resolved into the
|
||||
// generation config. The unified resolver folds CLI flags (e.g.
|
||||
// --openai-api-key), env vars, settings.security.auth.apiKey, and
|
||||
// modelProvider envKey lookups into this single value, so it is the same
|
||||
// key that refreshAuth will actually use at runtime. Validating against it
|
||||
// keeps pre-flight checks consistent with runtime behavior — without this,
|
||||
// CLI-provided credentials are silently ignored when no env var is set
|
||||
// (issue #3171).
|
||||
const resolvedApiKey = config
|
||||
?.getModelsConfig()
|
||||
.getGenerationConfig()?.apiKey;
|
||||
if (resolvedApiKey) {
|
||||
return {
|
||||
hasKey: true,
|
||||
checkedEnvKey: modelConfig?.envKey ?? DEFAULT_ENV_KEYS[authType],
|
||||
isExplicitEnvKey: !!modelConfig?.envKey,
|
||||
};
|
||||
}
|
||||
|
||||
if (modelConfig?.envKey) {
|
||||
// Explicit envKey configured - only check this env var, no apiKey fallback
|
||||
const hasKey = !!process.env[modelConfig.envKey];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue