fix(cli): recognize OpenAI-compatible providers in qwen auth status

Previously `qwen auth status` treated all `selectedType=openai` setups
as Coding Plan, checking only `BAILIAN_CODING_PLAN_API_KEY`. Users who
configured generic OpenAI-compatible providers (e.g. Xunfei, DeepSeek,
Ollama) via `modelProviders.openai` saw a misleading "Alibaba Cloud
Coding Plan (Incomplete)" even though their provider worked correctly.

Split the USE_OPENAI branch into two paths:
- Coding Plan: detected by `codingPlan.region` or `CODING_PLAN_ENV_KEY`
- Generic OpenAI-compatible: checks API key from modelProviders envKey,
  OPENAI_API_KEY, or settings.security.auth.apiKey; displays provider
  info including model name and base URL.

Closes #3612

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
jinye.djy 2026-04-26 01:16:30 +08:00
parent 83d1e6dcae
commit bf49da22fc
2 changed files with 301 additions and 37 deletions

View file

@ -8,6 +8,7 @@ import {
AuthType,
getErrorMessage,
type Config,
type ModelProvidersConfig,
type ProviderModelConfig as ModelConfig,
} from '@qwen-code/qwen-code-core';
import { writeStdoutLine, writeStderrLine } from '../../utils/stdioHelpers.js';
@ -39,6 +40,8 @@ interface MergedSettingsWithCodingPlan {
security?: {
auth?: {
selectedType?: string;
apiKey?: string;
baseUrl?: string;
};
};
codingPlan?: CodingPlanSettings;
@ -446,54 +449,110 @@ export async function showAuthStatus(): Promise<void> {
t('\n ⚠ Run /auth to switch to Coding Plan or another provider.\n'),
);
} else if (selectedType === AuthType.USE_OPENAI) {
// Check for Coding Plan configuration
const codingPlanRegion = mergedSettings.codingPlan?.region;
const codingPlanVersion = mergedSettings.codingPlan?.version;
const modelName = mergedSettings.model?.name;
// Check if API key is set in environment
const hasApiKey =
// Detect Coding Plan via region OR the presence of its env key
const hasCodingPlanKey =
!!process.env[CODING_PLAN_ENV_KEY] ||
!!mergedSettings.env?.[CODING_PLAN_ENV_KEY];
if (hasApiKey) {
writeStdoutLine(
t('✓ Authentication Method: Alibaba Cloud Coding Plan'),
);
if (codingPlanRegion || hasCodingPlanKey) {
// --- Coding Plan path ---
const codingPlanVersion = mergedSettings.codingPlan?.version;
if (codingPlanRegion) {
const regionDisplay =
codingPlanRegion === CodingPlanRegion.CHINA
? t('中国 (China) - 阿里云百炼')
: t('Global - Alibaba Cloud');
writeStdoutLine(t(' Region: {{region}}', { region: regionDisplay }));
}
if (modelName) {
if (hasCodingPlanKey) {
writeStdoutLine(
t(' Current Model: {{model}}', { model: modelName }),
t('✓ Authentication Method: Alibaba Cloud Coding Plan'),
);
if (codingPlanRegion) {
const regionDisplay =
codingPlanRegion === CodingPlanRegion.CHINA
? t('中国 (China) - 阿里云百炼')
: t('Global - Alibaba Cloud');
writeStdoutLine(
t(' Region: {{region}}', { region: regionDisplay }),
);
}
if (modelName) {
writeStdoutLine(
t(' Current Model: {{model}}', { model: modelName }),
);
}
if (codingPlanVersion) {
writeStdoutLine(
t(' Config Version: {{version}}', {
version: codingPlanVersion.substring(0, 8) + '...',
}),
);
}
writeStdoutLine(t(' Status: API key configured\n'));
} else {
writeStdoutLine(
t(
'⚠️ Authentication Method: Alibaba Cloud Coding Plan (Incomplete)',
),
);
writeStdoutLine(
t(' Issue: API key not found in environment or settings\n'),
);
writeStdoutLine(
t(' Run `qwen auth coding-plan` to re-configure.\n'),
);
}
if (codingPlanVersion) {
writeStdoutLine(
t(' Config Version: {{version}}', {
version: codingPlanVersion.substring(0, 8) + '...',
}),
);
}
writeStdoutLine(t(' Status: API key configured\n'));
} else {
writeStdoutLine(
t(
'⚠️ Authentication Method: Alibaba Cloud Coding Plan (Incomplete)',
),
);
writeStdoutLine(
t(' Issue: API key not found in environment or settings\n'),
);
writeStdoutLine(t(' Run `qwen auth coding-plan` to re-configure.\n'));
// --- Generic OpenAI-compatible provider path ---
const modelProviders = mergedSettings.modelProviders as
| ModelProvidersConfig
| undefined;
const models = modelProviders?.[AuthType.USE_OPENAI];
const modelConfig = Array.isArray(models)
? (models.find((m) => m.id === modelName) ?? models[0])
: undefined;
const hasApiKey =
!!(
modelConfig?.envKey &&
(process.env[modelConfig.envKey] ||
mergedSettings.env?.[modelConfig.envKey])
) ||
!!process.env['OPENAI_API_KEY'] ||
!!mergedSettings.env?.['OPENAI_API_KEY'] ||
!!mergedSettings.security?.auth?.apiKey;
if (hasApiKey) {
writeStdoutLine(
t('✓ Authentication Method: OpenAI-compatible Provider'),
);
if (modelName) {
writeStdoutLine(
t(' Current Model: {{model}}', { model: modelName }),
);
}
const baseUrl =
modelConfig?.baseUrl || mergedSettings.security?.auth?.baseUrl;
if (baseUrl) {
writeStdoutLine(t(' Base URL: {{baseUrl}}', { baseUrl }));
}
writeStdoutLine(t(' Status: API key configured\n'));
} else {
writeStdoutLine(
t(
'⚠️ Authentication Method: OpenAI-compatible Provider (Incomplete)',
),
);
writeStdoutLine(
t(' Issue: API key not found in environment or settings\n'),
);
writeStdoutLine(t(' Run `qwen auth` to re-configure.\n'));
}
}
} else {
writeStdoutLine(

View file

@ -144,6 +144,39 @@ describe('showAuthStatus', () => {
);
});
it('should show Coding Plan when env key is set but codingPlan.region is missing', async () => {
process.env[CODING_PLAN_ENV_KEY] = 'test-api-key';
vi.mocked(loadSettings).mockReturnValue(
createMockSettings({
security: {
auth: {
selectedType: AuthType.USE_OPENAI,
},
},
model: {
name: 'qwen3.5-plus',
},
}),
);
await showAuthStatus();
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('Alibaba Cloud Coding Plan'),
);
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('API key configured'),
);
expect(writeStdoutLine).not.toHaveBeenCalledWith(
expect.stringContaining('OpenAI-compatible Provider'),
);
expect(writeStdoutLine).not.toHaveBeenCalledWith(
expect.stringContaining('Region:'),
);
expect(process.exit).toHaveBeenCalledWith(0);
});
it('should show Coding Plan region for china', async () => {
process.env[CODING_PLAN_ENV_KEY] = 'test-api-key';
@ -262,4 +295,176 @@ describe('showAuthStatus', () => {
);
expect(process.exit).toHaveBeenCalledWith(1);
});
describe('OpenAI-compatible provider (no Coding Plan)', () => {
afterEach(() => {
delete process.env['OPENAI_API_KEY'];
delete process.env['CUSTOM_API_KEY'];
});
it('should show OpenAI-compatible status with OPENAI_API_KEY', async () => {
process.env['OPENAI_API_KEY'] = 'test-key';
vi.mocked(loadSettings).mockReturnValue(
createMockSettings({
security: {
auth: {
selectedType: AuthType.USE_OPENAI,
},
},
model: {
name: 'gpt-4o',
},
}),
);
await showAuthStatus();
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('OpenAI-compatible Provider'),
);
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('gpt-4o'),
);
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('API key configured'),
);
expect(writeStdoutLine).not.toHaveBeenCalledWith(
expect.stringContaining('Alibaba Cloud Coding Plan'),
);
expect(process.exit).toHaveBeenCalledWith(0);
});
it('should show OpenAI-compatible status with custom envKey from modelProviders', async () => {
process.env['CUSTOM_API_KEY'] = 'test-key';
vi.mocked(loadSettings).mockReturnValue(
createMockSettings({
security: {
auth: {
selectedType: AuthType.USE_OPENAI,
},
},
model: {
name: 'custom-model',
},
modelProviders: {
openai: [
{
id: 'custom-model',
envKey: 'CUSTOM_API_KEY',
baseUrl: 'https://custom-api.example.com/v1',
},
],
},
}),
);
await showAuthStatus();
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('OpenAI-compatible Provider'),
);
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('custom-model'),
);
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('https://custom-api.example.com/v1'),
);
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('API key configured'),
);
expect(process.exit).toHaveBeenCalledWith(0);
});
it('should show OpenAI-compatible status with settings.security.auth.apiKey', async () => {
vi.mocked(loadSettings).mockReturnValue(
createMockSettings({
security: {
auth: {
selectedType: AuthType.USE_OPENAI,
apiKey: 'settings-api-key',
baseUrl: 'https://my-provider.example.com/v1',
},
},
model: {
name: 'my-model',
},
}),
);
await showAuthStatus();
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('OpenAI-compatible Provider'),
);
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('https://my-provider.example.com/v1'),
);
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('API key configured'),
);
expect(process.exit).toHaveBeenCalledWith(0);
});
it('should show incomplete when no API key is found for OpenAI-compatible provider', async () => {
vi.mocked(loadSettings).mockReturnValue(
createMockSettings({
security: {
auth: {
selectedType: AuthType.USE_OPENAI,
},
},
}),
);
await showAuthStatus();
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('OpenAI-compatible Provider (Incomplete)'),
);
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('API key not found'),
);
expect(writeStdoutLine).not.toHaveBeenCalledWith(
expect.stringContaining('Alibaba Cloud Coding Plan'),
);
});
it('should detect API key via default model when model.name is unset', async () => {
process.env['CUSTOM_API_KEY'] = 'test-key';
vi.mocked(loadSettings).mockReturnValue(
createMockSettings({
security: {
auth: {
selectedType: AuthType.USE_OPENAI,
},
},
modelProviders: {
openai: [
{
id: 'default-model',
envKey: 'CUSTOM_API_KEY',
baseUrl: 'https://default-api.example.com/v1',
},
],
},
}),
);
await showAuthStatus();
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('OpenAI-compatible Provider'),
);
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('https://default-api.example.com/v1'),
);
expect(writeStdoutLine).toHaveBeenCalledWith(
expect.stringContaining('API key configured'),
);
expect(process.exit).toHaveBeenCalledWith(0);
});
});
});