mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-26 10:41:41 +00:00
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:
parent
83d1e6dcae
commit
bf49da22fc
2 changed files with 301 additions and 37 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue