Skip to content

Commit 2189e1d

Browse files
tanzhenxinqwencoder
andcommitted
fix(cli): block discontinued qwen-oauth model selection in ModelDialog
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>
1 parent 17269fa commit 2189e1d

File tree

9 files changed

+126
-31
lines changed

9 files changed

+126
-31
lines changed

packages/cli/src/i18n/locales/de.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,6 +1251,8 @@ export default {
12511251
'Das kostenlose Qwen OAuth-Kontingent wurde am 2026-04-15 eingestellt. Führen Sie /auth aus, um den Anbieter zu wechseln.',
12521252
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
12531253
'Das kostenlose Qwen OAuth-Kontingent wurde am 2026-04-15 eingestellt. Bitte wählen Sie Coding Plan oder API Key.',
1254+
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
1255+
'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.',
12541256
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
12551257
'\n⚠ Das kostenlose Qwen OAuth-Kontingent wurde am 2026-04-15 eingestellt. Bitte wählen Sie eine andere Option.\n',
12561258
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':

packages/cli/src/i18n/locales/en.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,8 @@ export default {
13041304
'Qwen OAuth free tier was discontinued on 2026-04-15. Run /auth to switch provider.',
13051305
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
13061306
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.',
1307+
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
1308+
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.',
13071309
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
13081310
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n',
13091311
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':

packages/cli/src/i18n/locales/fr.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,8 @@ export default {
13351335
'Le niveau gratuit Qwen OAuth a été abandonné le 2026-04-15. Exécutez /auth pour changer de fournisseur.',
13361336
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
13371337
'Le niveau gratuit Qwen OAuth a été abandonné le 2026-04-15. Veuillez sélectionner Coding Plan ou API Key.',
1338+
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
1339+
"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.",
13381340
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
13391341
'\n⚠ Le niveau gratuit Qwen OAuth a été abandonné le 2026-04-15. Veuillez sélectionner une autre option.\n',
13401342
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':

packages/cli/src/i18n/locales/ja.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,8 @@ export default {
972972
'Qwen OAuth 無料枠は 2026-04-15 に終了しました。/auth を実行してプロバイダーを切り替えてください。',
973973
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
974974
'Qwen OAuth 無料枠は 2026-04-15 に終了しました。Coding Plan または API Key を選択してください。',
975+
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
976+
'Qwen OAuth無料プランは2026-04-15に終了しました。他のプロバイダーのモデルを選択するか、/authを実行して切り替えてください。',
975977
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
976978
'\n⚠ Qwen OAuth 無料枠は 2026-04-15 に終了しました。他のオプションを選択してください。\n',
977979
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':

packages/cli/src/i18n/locales/pt.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,8 @@ export default {
12571257
'O nível gratuito do Qwen OAuth foi descontinuado em 2026-04-15. Execute /auth para trocar de provedor.',
12581258
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
12591259
'O nível gratuito do Qwen OAuth foi descontinuado em 2026-04-15. Selecione Coding Plan ou API Key.',
1260+
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
1261+
'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.',
12601262
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
12611263
'\n⚠ O nível gratuito do Qwen OAuth foi descontinuado em 2026-04-15. Selecione outra opção.\n',
12621264
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':

packages/cli/src/i18n/locales/ru.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,8 @@ export default {
11811181
'Бесплатный уровень Qwen OAuth прекращён 2026-04-15. Выполните /auth для смены провайдера.',
11821182
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
11831183
'Бесплатный уровень Qwen OAuth прекращён 2026-04-15. Выберите Coding Plan или API Key.',
1184+
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
1185+
'Бесплатный уровень Qwen OAuth был прекращен 2026-04-15. Пожалуйста, выберите модель от другого провайдера или выполните /auth для переключения.',
11841186
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
11851187
'\n⚠ Бесплатный уровень Qwen OAuth прекращён 2026-04-15. Выберите другую опцию.\n',
11861188
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':

packages/cli/src/i18n/locales/zh.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,8 @@ export default {
12331233
'Qwen OAuth 免费额度已于 2026-04-15 停用。请运行 /auth 切换服务商。',
12341234
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select Coding Plan or API Key instead.':
12351235
'Qwen OAuth 免费额度已于 2026-04-15 停用。请选择 Coding Plan 或 API Key。',
1236+
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.':
1237+
'Qwen OAuth免费层已于2026-04-15停止服务。请选择其他提供商的模型或运行 /auth 切换。',
12361238
'\n⚠ Qwen OAuth free tier was discontinued on 2026-04-15. Please select another option.\n':
12371239
'\n⚠ Qwen OAuth 免费额度已于 2026-04-15 停用。请选择其他选项。\n',
12381240
'Paid \u00B7 Up to 6,000 requests/5 hrs \u00B7 All Alibaba Cloud Coding Plan Models':

packages/cli/src/ui/components/ModelDialog.test.tsx

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,8 @@ describe('<ModelDialog />', () => {
192192
expect(mockedSelect).toHaveBeenCalledTimes(1);
193193
});
194194

195-
it('calls config.switchModel and onClose when DescriptiveRadioButtonSelect.onSelect is triggered', async () => {
196-
const { props, mockConfig, mockSettings } = renderComponent(
195+
it('blocks qwen-oauth model selection with an error message (discontinued)', async () => {
196+
const { props, mockConfig } = renderComponent(
197197
{},
198198
{
199199
getAvailableModelsForAuthType: vi.fn((t: AuthType) => {
@@ -214,25 +214,79 @@ describe('<ModelDialog />', () => {
214214

215215
await childOnSelect(`${AuthType.QWEN_OAUTH}::${DEFAULT_QWEN_MODEL}`);
216216

217-
expect(mockConfig?.switchModel).toHaveBeenCalledWith(
218-
AuthType.QWEN_OAUTH,
219-
DEFAULT_QWEN_MODEL,
217+
// qwen-oauth is discontinued — switchModel should NOT be called
218+
expect(mockConfig?.switchModel).not.toHaveBeenCalled();
219+
// Dialog should NOT close (user stays in the dialog to see the error)
220+
expect(props.onClose).not.toHaveBeenCalled();
221+
});
222+
223+
it('calls config.switchModel and onClose when selecting a non-OAuth model', async () => {
224+
const switchModel = vi.fn().mockResolvedValue(undefined);
225+
const getAuthType = vi.fn(() => AuthType.USE_OPENAI);
226+
const getAvailableModelsForAuthType = vi.fn((t: AuthType) => {
227+
if (t === AuthType.USE_OPENAI) {
228+
return [{ id: 'gpt-4', label: 'GPT-4', authType: t }];
229+
}
230+
if (t === AuthType.QWEN_OAUTH) {
231+
return getFilteredQwenModels().map((m) => ({
232+
id: m.id,
233+
label: m.label,
234+
authType: AuthType.QWEN_OAUTH,
235+
}));
236+
}
237+
return [];
238+
});
239+
240+
const { props, mockSettings } = renderComponent({}, {
241+
getModel: vi.fn(() => 'gpt-4'),
242+
getAuthType,
243+
switchModel,
244+
getAvailableModelsForAuthType,
245+
getAllConfiguredModels: vi.fn(() => [
246+
...getFilteredQwenModels().map((m) => ({
247+
id: m.id,
248+
label: m.label,
249+
description: m.description || '',
250+
authType: AuthType.QWEN_OAUTH,
251+
})),
252+
{
253+
id: 'gpt-4',
254+
label: 'GPT-4',
255+
description: 'GPT-4 model',
256+
authType: AuthType.USE_OPENAI,
257+
},
258+
]),
259+
getContentGeneratorConfig: vi.fn(() => ({
260+
authType: AuthType.USE_OPENAI,
261+
model: 'gpt-4',
262+
})),
263+
} as unknown as Partial<Config>);
264+
265+
const childOnSelect = mockedSelect.mock.calls[0][0].onSelect;
266+
expect(childOnSelect).toBeDefined();
267+
268+
// Select a non-OAuth model (USE_OPENAI)
269+
await childOnSelect(`${AuthType.USE_OPENAI}::gpt-4`);
270+
271+
expect(switchModel).toHaveBeenCalledWith(
272+
AuthType.USE_OPENAI,
273+
'gpt-4',
220274
undefined,
221275
);
222276
expect(mockSettings.setValue).toHaveBeenCalledWith(
223277
SettingScope.User,
224278
'model.name',
225-
DEFAULT_QWEN_MODEL,
279+
'gpt-4',
226280
);
227281
expect(mockSettings.setValue).toHaveBeenCalledWith(
228282
SettingScope.User,
229283
'security.auth.selectedType',
230-
AuthType.QWEN_OAUTH,
284+
AuthType.USE_OPENAI,
231285
);
232286
expect(props.onClose).toHaveBeenCalledTimes(1);
233287
});
234288

235-
it('calls config.switchModel and persists authType+model when selecting a different authType', async () => {
289+
it('blocks switching to qwen-oauth from another authType (discontinued)', async () => {
236290
const switchModel = vi.fn().mockResolvedValue(undefined);
237291
const getAuthType = vi.fn(() => AuthType.USE_OPENAI);
238292
const getAvailableModelsForAuthType = vi.fn((t: AuthType) => {
@@ -253,39 +307,25 @@ describe('<ModelDialog />', () => {
253307
getAuthType,
254308
getModel: vi.fn(() => 'gpt-4'),
255309
getContentGeneratorConfig: vi.fn(() => ({
256-
authType: AuthType.QWEN_OAUTH,
257-
model: DEFAULT_QWEN_MODEL,
310+
authType: AuthType.USE_OPENAI,
311+
model: 'gpt-4',
258312
})),
259-
// Add switchModel to the mock object (not the type)
260313
switchModel,
261314
getAvailableModelsForAuthType,
262315
};
263316

264-
const { props, mockSettings } = renderComponent(
317+
const { props } = renderComponent(
265318
{},
266-
// Cast to Config to bypass type checking, matching the runtime behavior
267319
mockConfigWithSwitchAuthType as unknown as Partial<Config>,
268320
);
269321

270322
const childOnSelect = mockedSelect.mock.calls[0][0].onSelect;
271323
await childOnSelect(`${AuthType.QWEN_OAUTH}::${DEFAULT_QWEN_MODEL}`);
272324

273-
expect(switchModel).toHaveBeenCalledWith(
274-
AuthType.QWEN_OAUTH,
275-
DEFAULT_QWEN_MODEL,
276-
{ requireCachedCredentials: true },
277-
);
278-
expect(mockSettings.setValue).toHaveBeenCalledWith(
279-
SettingScope.User,
280-
'model.name',
281-
DEFAULT_QWEN_MODEL,
282-
);
283-
expect(mockSettings.setValue).toHaveBeenCalledWith(
284-
SettingScope.User,
285-
'security.auth.selectedType',
286-
AuthType.QWEN_OAUTH,
287-
);
288-
expect(props.onClose).toHaveBeenCalledTimes(1);
325+
// qwen-oauth is discontinued — switchModel should NOT be called
326+
expect(switchModel).not.toHaveBeenCalled();
327+
// Dialog should NOT close
328+
expect(props.onClose).not.toHaveBeenCalled();
289329
});
290330

291331
it('passes onHighlight to DescriptiveRadioButtonSelect', () => {

packages/cli/src/ui/components/ModelDialog.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,28 +213,42 @@ export function ModelDialog({
213213
const value =
214214
isRuntime && snapshotId ? snapshotId : `${t2}::${model.id}`;
215215

216+
const isQwenOAuth = t2 === AuthType.QWEN_OAUTH;
217+
216218
const title = (
217219
<Text>
218220
<Text
219221
bold
220-
color={isRuntime ? theme.status.warning : theme.text.accent}
222+
color={
223+
isQwenOAuth
224+
? theme.status.warning
225+
: isRuntime
226+
? theme.status.warning
227+
: theme.text.accent
228+
}
221229
>
222230
[{t2}]
223231
</Text>
224232
<Text>{` ${model.label}`}</Text>
225233
{isRuntime && (
226234
<Text color={theme.status.warning}> (Runtime)</Text>
227235
)}
236+
{isQwenOAuth && !isRuntime && (
237+
<Text color={theme.status.warning}> ({t('Discontinued')})</Text>
238+
)}
228239
</Text>
229240
);
230241

231-
// Include runtime indicator in description
242+
// Include runtime / discontinued indicator in description
232243
let description = model.description || '';
233244
if (isRuntime) {
234245
description = description
235246
? `${description} (Runtime)`
236247
: 'Runtime model';
237248
}
249+
if (isQwenOAuth && !isRuntime) {
250+
description = t('Discontinued — switch to Coding Plan or API Key');
251+
}
238252

239253
return {
240254
value,
@@ -323,6 +337,25 @@ export function ModelDialog({
323337
return;
324338
}
325339

340+
// Block selection of discontinued qwen-oauth models
341+
// (only block non-runtime OAuth; runtime OAuth models from existing
342+
// cached tokens are still allowed to work until the server rejects them)
343+
const isQwenOAuthSelection =
344+
selected.startsWith(`${AuthType.QWEN_OAUTH}::`) ||
345+
(selected.startsWith('$runtime|') &&
346+
selected.split('|')[1] === AuthType.QWEN_OAUTH);
347+
const isRuntimeOAuthSelection = selected.startsWith(
348+
`$runtime|${AuthType.QWEN_OAUTH}|`,
349+
);
350+
if (isQwenOAuthSelection && !isRuntimeOAuthSelection) {
351+
setErrorMessage(
352+
t(
353+
'Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider or run /auth to switch.',
354+
),
355+
);
356+
return;
357+
}
358+
326359
let after: ContentGeneratorConfig | undefined;
327360
let effectiveAuthType: AuthType | undefined;
328361
let effectiveModelId = selected;
@@ -461,6 +494,14 @@ export function ModelDialog({
461494
borderRight={false}
462495
borderColor={theme.border.default}
463496
/>
497+
{highlightedEntry.authType === AuthType.QWEN_OAUTH &&
498+
!highlightedEntry.isRuntime && (
499+
<Box marginTop={1}>
500+
<Text color={theme.status.warning}>
501+
{t('Discontinued — switch to Coding Plan or API Key')}
502+
</Text>
503+
</Box>
504+
)}
464505
<DetailRow
465506
label={t('Modality')}
466507
value={formatModalities(highlightedEntry.model.modalities)}

0 commit comments

Comments
 (0)