-
Notifications
You must be signed in to change notification settings - Fork 2.4k
fix(cli): keep /model switches session-scoped #4332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
a101ca8
412c8d6
ef5e575
0779951
eab2ece
fd46f47
f832609
1faa612
346da6e
ce79a71
0b5a1ff
063fef6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -32,7 +32,6 @@ function persistSetting( | |||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| async function switchMainModel( | ||||||||||||||||||||||||||||||
| config: Config, | ||||||||||||||||||||||||||||||
| settings: LoadedSettings, | ||||||||||||||||||||||||||||||
| currentAuthType: AuthType, | ||||||||||||||||||||||||||||||
| modelArg: string, | ||||||||||||||||||||||||||||||
| ): Promise<string> { | ||||||||||||||||||||||||||||||
|
|
@@ -47,12 +46,26 @@ async function switchMainModel( | |||||||||||||||||||||||||||||
| ? { requireCachedCredentials: true } | ||||||||||||||||||||||||||||||
| : undefined, | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| return parsed.modelId; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| await config.switchModel(currentAuthType, modelArg, undefined); | ||||||||||||||||||||||||||||||
| return modelArg; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| function persistMainModelDefault( | ||||||||||||||||||||||||||||||
| settings: LoadedSettings, | ||||||||||||||||||||||||||||||
| currentAuthType: AuthType, | ||||||||||||||||||||||||||||||
| modelArg: string, | ||||||||||||||||||||||||||||||
| ): string { | ||||||||||||||||||||||||||||||
| const parsed = parseAcpModelOption(modelArg); | ||||||||||||||||||||||||||||||
| if (parsed.authType) { | ||||||||||||||||||||||||||||||
| persistSetting(settings, 'security.auth.selectedType', parsed.authType); | ||||||||||||||||||||||||||||||
| persistSetting(settings, 'model.name', parsed.modelId); | ||||||||||||||||||||||||||||||
| return parsed.modelId; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| await config.switchModel(currentAuthType, modelArg, undefined); | ||||||||||||||||||||||||||||||
| persistSetting(settings, 'security.auth.selectedType', currentAuthType); | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] The Renaming to — qwen-latest-series-invite-beta-v34 via Qwen Code /review |
||||||||||||||||||||||||||||||
| persistSetting(settings, 'model.name', modelArg); | ||||||||||||||||||||||||||||||
| return modelArg; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
@@ -114,26 +127,40 @@ export const modelCommand: SlashCommand = { | |||||||||||||||||||||||||||||
| completionPriority: 100, | ||||||||||||||||||||||||||||||
| get description() { | ||||||||||||||||||||||||||||||
| return t( | ||||||||||||||||||||||||||||||
| 'Switch the model for this session (--fast for suggestion model, [model-id] to switch immediately).', | ||||||||||||||||||||||||||||||
| 'Switch the model for this session (--default to persist, --fast for suggestion model).', | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| argumentHint: '[--fast] [<model-id>]', | ||||||||||||||||||||||||||||||
| argumentHint: '[--default|--fast] [<model-id>]', | ||||||||||||||||||||||||||||||
| kind: CommandKind.BUILT_IN, | ||||||||||||||||||||||||||||||
| supportedModes: ['interactive', 'non_interactive', 'acp'] as const, | ||||||||||||||||||||||||||||||
| completion: async (context, partialArg) => { | ||||||||||||||||||||||||||||||
| if (partialArg && '--fast'.startsWith(partialArg)) { | ||||||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| value: '--fast', | ||||||||||||||||||||||||||||||
| description: t( | ||||||||||||||||||||||||||||||
| 'Set a lighter model for prompt suggestions and speculative execution', | ||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||
| } else if (partialArg.trim()) { | ||||||||||||||||||||||||||||||
| const trimmedPartialArg = partialArg.trim(); | ||||||||||||||||||||||||||||||
| if (trimmedPartialArg.startsWith('--default ')) { | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] The completion function has a
Suggested change
Also: the completion logic (3 new branches + the — qwen-latest-series-invite-beta-v34 via Qwen Code /review |
||||||||||||||||||||||||||||||
| const modelPartial = trimmedPartialArg.replace('--default', '').trim(); | ||||||||||||||||||||||||||||||
| return getAvailableModelIds(context) | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Tab-completion will never surface cross-provider models, so users can't discover the cross-provider — qwen-latest-series-invite-beta-v34 via Qwen Code /review |
||||||||||||||||||||||||||||||
| .filter((id) => id.startsWith(modelPartial)) | ||||||||||||||||||||||||||||||
| .map((id) => `--default ${id}`); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const flagCompletions = [ | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| value: '--default', | ||||||||||||||||||||||||||||||
| description: t('Persist the selected model as the default'), | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Four new Add these entries to — qwen-latest-series-invite-beta-v34 via Qwen Code /review |
||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| value: '--fast', | ||||||||||||||||||||||||||||||
| description: t( | ||||||||||||||||||||||||||||||
| 'Set a lighter model for prompt suggestions and speculative execution', | ||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| ].filter((completion) => completion.value.startsWith(trimmedPartialArg)); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (flagCompletions.length > 0) { | ||||||||||||||||||||||||||||||
| return flagCompletions; | ||||||||||||||||||||||||||||||
| } else if (trimmedPartialArg) { | ||||||||||||||||||||||||||||||
| // Include model IDs matching the partial argument | ||||||||||||||||||||||||||||||
| return getAvailableModelIds(context).filter((id) => | ||||||||||||||||||||||||||||||
| id.startsWith(partialArg.trim()), | ||||||||||||||||||||||||||||||
| id.startsWith(trimmedPartialArg), | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||
|
|
@@ -156,6 +183,8 @@ export const modelCommand: SlashCommand = { | |||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Handle --fast flag: /model --fast <modelName> | ||||||||||||||||||||||||||||||
| const args = context.invocation?.args?.trim() || actionArgs.trim(); | ||||||||||||||||||||||||||||||
| const isDefaultModelCommand = | ||||||||||||||||||||||||||||||
| args === '--default' || args.startsWith('--default '); | ||||||||||||||||||||||||||||||
| const isFastModelCommand = args === '--fast' || args.startsWith('--fast '); | ||||||||||||||||||||||||||||||
| if (isFastModelCommand) { | ||||||||||||||||||||||||||||||
| const modelName = args.replace('--fast', '').trim(); | ||||||||||||||||||||||||||||||
|
|
@@ -256,9 +285,11 @@ export const modelCommand: SlashCommand = { | |||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const modelName = args.trim().split(/\s+/)[0] ?? ''; | ||||||||||||||||||||||||||||||
| const modelName = isDefaultModelCommand | ||||||||||||||||||||||||||||||
| ? args.replace('--default', '').trim().split(/\s+/)[0] | ||||||||||||||||||||||||||||||
| : (args.trim().split(/\s+/)[0] ?? ''); | ||||||||||||||||||||||||||||||
| if (modelName) { | ||||||||||||||||||||||||||||||
| if (!settings) { | ||||||||||||||||||||||||||||||
| if (isDefaultModelCommand && !settings) { | ||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||
| type: 'message', | ||||||||||||||||||||||||||||||
| messageType: 'error', | ||||||||||||||||||||||||||||||
|
|
@@ -283,14 +314,27 @@ export const modelCommand: SlashCommand = { | |||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| const effectiveModelName = await switchMainModel( | ||||||||||||||||||||||||||||||
| config, | ||||||||||||||||||||||||||||||
| settings, | ||||||||||||||||||||||||||||||
| authType, | ||||||||||||||||||||||||||||||
| modelName, | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| if (isDefaultModelCommand) { | ||||||||||||||||||||||||||||||
| persistMainModelDefault(settings, authType, modelName); | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] The dialog's
On next startup, the app tries the discontinued auth path and fails. Consider adding a QWEN_OAUTH guard mirroring the dialog: if (isDefaultModelCommand && authType === AuthType.QWEN_OAUTH) {
return {
type: 'message',
messageType: 'error',
content: t('Qwen OAuth free tier was discontinued on 2026-04-15. Please select a model from another provider.'),
};
}— qwen-latest-series-invite-beta-v34 via Qwen Code /review
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] The dialog
Suggested change
— qwen-latest-series-invite-beta-v34 via Qwen Code /review |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||
| type: 'message', | ||||||||||||||||||||||||||||||
| messageType: 'info', | ||||||||||||||||||||||||||||||
| content: t('Model') + ': ' + effectiveModelName, | ||||||||||||||||||||||||||||||
| content: | ||||||||||||||||||||||||||||||
| (isDefaultModelCommand ? t('Default model') : t('Model')) + | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] The success message for session-only switches ( The non-interactive help text at line 359 explicitly says "(session only)", but the interactive success message omits this qualifier.
Suggested change
— qwen-latest-series-invite-beta-v34 via Qwen Code /review |
||||||||||||||||||||||||||||||
| ': ' + | ||||||||||||||||||||||||||||||
| effectiveModelName, | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (isDefaultModelCommand) { | ||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||
| type: 'message', | ||||||||||||||||||||||||||||||
| messageType: 'error', | ||||||||||||||||||||||||||||||
| content: t('Usage: /model --default <model-id>'), | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Suggestion] Several new code paths introduced by this PR lack test coverage:
persistMainModelDefaultwith provider-qualified model ID — theif (parsed.authType)branch (line 62) is never exercised. The only--defaulttest uses the unqualifiedqwen-max, which hits the else branch.--defaultwith nullsettings— theif (isDefaultModelCommand && !settings)guard (line 291) is a new conditional with no test.dkey error path — thecatchblock in ModelDialog.tsx (line 432) whenconfig.switchModelthrows is untested.dkey in fast model mode — no test renders<ModelDialog isFastModelMode={true} />and pressesdto verify it's a no-op.Consider adding tests for these branches to prevent regression.
— qwen-latest-series-invite-beta-v34 via Qwen Code /review