mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 03:30:40 +00:00
fix(cli): block discontinued qwen-oauth model selection in ModelDialog (#3299)
Some checks are pending
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
Some checks are pending
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
PR #3291 discontinued the Qwen OAuth free tier but intentionally left the ModelDialog unchanged, relying on server rejection for qwen-oauth models. This follow-up adds proper UI handling consistent with the AuthDialog: - Mark qwen-oauth model entries with "(Discontinued)" label and warning color - Replace descriptions with "Discontinued — switch to Coding Plan or API Key" - Block selection with inline error message instead of calling switchModel - Show ⚠ discontinuation notice in the detail panel for highlighted entries - Runtime OAuth models (existing cached tokens) remain selectable until server rejects them (soft cutoff principle from PR #3291) - Add i18n strings for the new error message across all 7 locale files Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
17269fa0e6
commit
a6612940f8
9 changed files with 126 additions and 31 deletions
|
|
@ -1251,6 +1251,8 @@ export default {
|
|||
'Das kostenlose Qwen OAuth-Kontingent wurde am 2026-04-15 eingestellt. Führen Sie /auth aus, um den Anbieter zu wechseln.',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
|
||||
'Das kostenlose Qwen OAuth-Kontingent wurde am 2026-04-15 eingestellt. Bitte wählen Sie Coding Plan oder API Key.',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
|
||||
'Das kostenlose Qwen OAuth-Angebot wurde am 2026-04-15 eingestellt. Bitte wählen Sie ein Modell eines anderen Anbieter oder führen Sie /auth aus, um zu wechseln.',
|
||||
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
|
||||
'\n⚠ Das kostenlose Qwen OAuth-Kontingent wurde am 2026-04-15 eingestellt. Bitte wählen Sie eine andere Option.\n',
|
||||
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':
|
||||
|
|
|
|||
|
|
@ -1304,6 +1304,8 @@ export default {
|
|||
'Qwen OAuth free tier was discontinued on 2026-04-15. Run /auth to switch provider.',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.',
|
||||
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
|
||||
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n',
|
||||
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':
|
||||
|
|
|
|||
|
|
@ -1335,6 +1335,8 @@ export default {
|
|||
'Le niveau gratuit Qwen OAuth a été abandonné le 2026-04-15. Exécutez /auth pour changer de fournisseur.',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
|
||||
'Le niveau gratuit Qwen OAuth a été abandonné le 2026-04-15. Veuillez sélectionner Coding Plan ou API Key.',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
|
||||
"Le niveau gratuit de Qwen OAuth a été abandonné le 2026-04-15. Veuillez sélectionner un modèle d'un autre fournisseur ou exécuter /auth pour changer.",
|
||||
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
|
||||
'\n⚠ Le niveau gratuit Qwen OAuth a été abandonné le 2026-04-15. Veuillez sélectionner une autre option.\n',
|
||||
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':
|
||||
|
|
|
|||
|
|
@ -972,6 +972,8 @@ export default {
|
|||
'Qwen OAuth 無料枠は 2026-04-15 に終了しました。/auth を実行してプロバイダーを切り替えてください。',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
|
||||
'Qwen OAuth 無料枠は 2026-04-15 に終了しました。Coding Plan または API Key を選択してください。',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
|
||||
'Qwen OAuth無料プランは2026-04-15に終了しました。他のプロバイダーのモデルを選択するか、/authを実行して切り替えてください。',
|
||||
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
|
||||
'\n⚠ Qwen OAuth 無料枠は 2026-04-15 に終了しました。他のオプションを選択してください。\n',
|
||||
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':
|
||||
|
|
|
|||
|
|
@ -1257,6 +1257,8 @@ export default {
|
|||
'O nível gratuito do Qwen OAuth foi descontinuado em 2026-04-15. Execute /auth para trocar de provedor.',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
|
||||
'O nível gratuito do Qwen OAuth foi descontinuado em 2026-04-15. Selecione Coding Plan ou API Key.',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
|
||||
'O nível gratuito do Qwen OAuth foi descontinuado em 2026-04-15. Por favor, selecione um modelo de outro provedor ou execute /auth para trocar.',
|
||||
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
|
||||
'\n⚠ O nível gratuito do Qwen OAuth foi descontinuado em 2026-04-15. Selecione outra opção.\n',
|
||||
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':
|
||||
|
|
|
|||
|
|
@ -1181,6 +1181,8 @@ export default {
|
|||
'Бесплатный уровень Qwen OAuth прекращён 2026-04-15. Выполните /auth для смены провайдера.',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
|
||||
'Бесплатный уровень Qwen OAuth прекращён 2026-04-15. Выберите Coding Plan или API Key.',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
|
||||
'Бесплатный уровень Qwen OAuth был прекращен 2026-04-15. Пожалуйста, выберите модель от другого провайдера или выполните /auth для переключения.',
|
||||
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
|
||||
'\n⚠ Бесплатный уровень Qwen OAuth прекращён 2026-04-15. Выберите другую опцию.\n',
|
||||
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':
|
||||
|
|
|
|||
|
|
@ -1233,6 +1233,8 @@ export default {
|
|||
'Qwen OAuth 免费额度已于 2026-04-15 停用。请运行 /auth 切换服务商。',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
|
||||
'Qwen OAuth 免费额度已于 2026-04-15 停用。请选择 Coding Plan 或 API Key。',
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
|
||||
'Qwen OAuth免费层已于2026-04-15停止服务。请选择其他提供商的模型或运行 /auth 切换。',
|
||||
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
|
||||
'\n⚠ Qwen OAuth 免费额度已于 2026-04-15 停用。请选择其他选项。\n',
|
||||
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':
|
||||
|
|
|
|||
|
|
@ -192,8 +192,8 @@ describe('<ModelDialog />', () => {
|
|||
expect(mockedSelect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls config.switchModel and onClose when DescriptiveRadioButtonSelect.onSelect is triggered', async () => {
|
||||
const { props, mockConfig, mockSettings } = renderComponent(
|
||||
it('blocks qwen-oauth model selection with an error message (discontinued)', async () => {
|
||||
const { props, mockConfig } = renderComponent(
|
||||
{},
|
||||
{
|
||||
getAvailableModelsForAuthType: vi.fn((t: AuthType) => {
|
||||
|
|
@ -214,25 +214,79 @@ describe('<ModelDialog />', () => {
|
|||
|
||||
await childOnSelect(`${AuthType.QWEN_OAUTH}::${DEFAULT_QWEN_MODEL}`);
|
||||
|
||||
expect(mockConfig?.switchModel).toHaveBeenCalledWith(
|
||||
AuthType.QWEN_OAUTH,
|
||||
DEFAULT_QWEN_MODEL,
|
||||
// qwen-oauth is discontinued — switchModel should NOT be called
|
||||
expect(mockConfig?.switchModel).not.toHaveBeenCalled();
|
||||
// Dialog should NOT close (user stays in the dialog to see the error)
|
||||
expect(props.onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls config.switchModel and onClose when selecting a non-OAuth model', async () => {
|
||||
const switchModel = vi.fn().mockResolvedValue(undefined);
|
||||
const getAuthType = vi.fn(() => AuthType.USE_OPENAI);
|
||||
const getAvailableModelsForAuthType = vi.fn((t: AuthType) => {
|
||||
if (t === AuthType.USE_OPENAI) {
|
||||
return [{ id: 'gpt-4', label: 'GPT-4', authType: t }];
|
||||
}
|
||||
if (t === AuthType.QWEN_OAUTH) {
|
||||
return getFilteredQwenModels().map((m) => ({
|
||||
id: m.id,
|
||||
label: m.label,
|
||||
authType: AuthType.QWEN_OAUTH,
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const { props, mockSettings } = renderComponent({}, {
|
||||
getModel: vi.fn(() => 'gpt-4'),
|
||||
getAuthType,
|
||||
switchModel,
|
||||
getAvailableModelsForAuthType,
|
||||
getAllConfiguredModels: vi.fn(() => [
|
||||
...getFilteredQwenModels().map((m) => ({
|
||||
id: m.id,
|
||||
label: m.label,
|
||||
description: m.description || '',
|
||||
authType: AuthType.QWEN_OAUTH,
|
||||
})),
|
||||
{
|
||||
id: 'gpt-4',
|
||||
label: 'GPT-4',
|
||||
description: 'GPT-4 model',
|
||||
authType: AuthType.USE_OPENAI,
|
||||
},
|
||||
]),
|
||||
getContentGeneratorConfig: vi.fn(() => ({
|
||||
authType: AuthType.USE_OPENAI,
|
||||
model: 'gpt-4',
|
||||
})),
|
||||
} as unknown as Partial<Config>);
|
||||
|
||||
const childOnSelect = mockedSelect.mock.calls[0][0].onSelect;
|
||||
expect(childOnSelect).toBeDefined();
|
||||
|
||||
// Select a non-OAuth model (USE_OPENAI)
|
||||
await childOnSelect(`${AuthType.USE_OPENAI}::gpt-4`);
|
||||
|
||||
expect(switchModel).toHaveBeenCalledWith(
|
||||
AuthType.USE_OPENAI,
|
||||
'gpt-4',
|
||||
undefined,
|
||||
);
|
||||
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||
SettingScope.User,
|
||||
'model.name',
|
||||
DEFAULT_QWEN_MODEL,
|
||||
'gpt-4',
|
||||
);
|
||||
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||
SettingScope.User,
|
||||
'security.auth.selectedType',
|
||||
AuthType.QWEN_OAUTH,
|
||||
AuthType.USE_OPENAI,
|
||||
);
|
||||
expect(props.onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls config.switchModel and persists authType+model when selecting a different authType', async () => {
|
||||
it('blocks switching to qwen-oauth from another authType (discontinued)', async () => {
|
||||
const switchModel = vi.fn().mockResolvedValue(undefined);
|
||||
const getAuthType = vi.fn(() => AuthType.USE_OPENAI);
|
||||
const getAvailableModelsForAuthType = vi.fn((t: AuthType) => {
|
||||
|
|
@ -253,39 +307,25 @@ describe('<ModelDialog />', () => {
|
|||
getAuthType,
|
||||
getModel: vi.fn(() => 'gpt-4'),
|
||||
getContentGeneratorConfig: vi.fn(() => ({
|
||||
authType: AuthType.QWEN_OAUTH,
|
||||
model: DEFAULT_QWEN_MODEL,
|
||||
authType: AuthType.USE_OPENAI,
|
||||
model: 'gpt-4',
|
||||
})),
|
||||
// Add switchModel to the mock object (not the type)
|
||||
switchModel,
|
||||
getAvailableModelsForAuthType,
|
||||
};
|
||||
|
||||
const { props, mockSettings } = renderComponent(
|
||||
const { props } = renderComponent(
|
||||
{},
|
||||
// Cast to Config to bypass type checking, matching the runtime behavior
|
||||
mockConfigWithSwitchAuthType as unknown as Partial<Config>,
|
||||
);
|
||||
|
||||
const childOnSelect = mockedSelect.mock.calls[0][0].onSelect;
|
||||
await childOnSelect(`${AuthType.QWEN_OAUTH}::${DEFAULT_QWEN_MODEL}`);
|
||||
|
||||
expect(switchModel).toHaveBeenCalledWith(
|
||||
AuthType.QWEN_OAUTH,
|
||||
DEFAULT_QWEN_MODEL,
|
||||
{ requireCachedCredentials: true },
|
||||
);
|
||||
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||
SettingScope.User,
|
||||
'model.name',
|
||||
DEFAULT_QWEN_MODEL,
|
||||
);
|
||||
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||
SettingScope.User,
|
||||
'security.auth.selectedType',
|
||||
AuthType.QWEN_OAUTH,
|
||||
);
|
||||
expect(props.onClose).toHaveBeenCalledTimes(1);
|
||||
// qwen-oauth is discontinued — switchModel should NOT be called
|
||||
expect(switchModel).not.toHaveBeenCalled();
|
||||
// Dialog should NOT close
|
||||
expect(props.onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('passes onHighlight to DescriptiveRadioButtonSelect', () => {
|
||||
|
|
|
|||
|
|
@ -213,11 +213,19 @@ export function ModelDialog({
|
|||
const value =
|
||||
isRuntime && snapshotId ? snapshotId : `${t2}::${model.id}`;
|
||||
|
||||
const isQwenOAuth = t2 === AuthType.QWEN_OAUTH;
|
||||
|
||||
const title = (
|
||||
<Text>
|
||||
<Text
|
||||
bold
|
||||
color={isRuntime ? theme.status.warning : theme.text.accent}
|
||||
color={
|
||||
isQwenOAuth
|
||||
? theme.status.warning
|
||||
: isRuntime
|
||||
? theme.status.warning
|
||||
: theme.text.accent
|
||||
}
|
||||
>
|
||||
[{t2}]
|
||||
</Text>
|
||||
|
|
@ -225,16 +233,22 @@ export function ModelDialog({
|
|||
{isRuntime && (
|
||||
<Text color={theme.status.warning}> (Runtime)</Text>
|
||||
)}
|
||||
{isQwenOAuth && !isRuntime && (
|
||||
<Text color={theme.status.warning}> ({t('Discontinued')})</Text>
|
||||
)}
|
||||
</Text>
|
||||
);
|
||||
|
||||
// Include runtime indicator in description
|
||||
// Include runtime / discontinued indicator in description
|
||||
let description = model.description || '';
|
||||
if (isRuntime) {
|
||||
description = description
|
||||
? `${description} (Runtime)`
|
||||
: 'Runtime model';
|
||||
}
|
||||
if (isQwenOAuth && !isRuntime) {
|
||||
description = t('Discontinued — switch to Coding Plan or API Key');
|
||||
}
|
||||
|
||||
return {
|
||||
value,
|
||||
|
|
@ -323,6 +337,25 @@ export function ModelDialog({
|
|||
return;
|
||||
}
|
||||
|
||||
// Block selection of discontinued qwen-oauth models
|
||||
// (only block non-runtime OAuth; runtime OAuth models from existing
|
||||
// cached tokens are still allowed to work until the server rejects them)
|
||||
const isQwenOAuthSelection =
|
||||
selected.startsWith(`${AuthType.QWEN_OAUTH}::`) ||
|
||||
(selected.startsWith('$runtime|') &&
|
||||
selected.split('|')[1] === AuthType.QWEN_OAUTH);
|
||||
const isRuntimeOAuthSelection = selected.startsWith(
|
||||
`$runtime|${AuthType.QWEN_OAUTH}|`,
|
||||
);
|
||||
if (isQwenOAuthSelection && !isRuntimeOAuthSelection) {
|
||||
setErrorMessage(
|
||||
t(
|
||||
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let after: ContentGeneratorConfig | undefined;
|
||||
let effectiveAuthType: AuthType | undefined;
|
||||
let effectiveModelId = selected;
|
||||
|
|
@ -461,6 +494,14 @@ export function ModelDialog({
|
|||
borderRight={false}
|
||||
borderColor={theme.border.default}
|
||||
/>
|
||||
{highlightedEntry.authType === AuthType.QWEN_OAUTH &&
|
||||
!highlightedEntry.isRuntime && (
|
||||
<Box marginTop={1}>
|
||||
<Text color={theme.status.warning}>
|
||||
⚠ {t('Discontinued — switch to Coding Plan or API Key')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
<DetailRow
|
||||
label={t('Modality')}
|
||||
value={formatModalities(highlightedEntry.model.modalities)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue