mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-01 21:20:44 +00:00
fix: align authType & model persisting behavior across dialogs
This commit is contained in:
parent
fe2ed889b9
commit
afe6ba255e
7 changed files with 26 additions and 33 deletions
|
|
@ -219,7 +219,10 @@ Per-field precedence for `generationConfig`:
|
||||||
|
|
||||||
##### Selection persistence and recommendations
|
##### Selection persistence and recommendations
|
||||||
|
|
||||||
- `/model` persists both `model.name` and `security.auth.selectedType`. We first try to write to the nearest scope whose settings already contain `modelProviders`; otherwise we fall back to user scope. Qwen OAuth selections always persist to the user scope.
|
> [!important]
|
||||||
|
> Define `modelProviders` in the user-scope `~/.qwen/settings.json` whenever possible and avoid persisting credential overrides in any scope. Keeping the provider catalog in user settings prevents merge/override conflicts between project and user scopes and ensures `/auth` and `/model` updates always write back to a consistent scope.
|
||||||
|
|
||||||
|
- `/model` and `/auth` persist `model.name` (where applicable) and `security.auth.selectedType` to the closest writable scope that already defines `modelProviders`; otherwise they fall back to the user scope. This keeps workspace/user files in sync with the active provider catalog.
|
||||||
- Without `modelProviders`, the resolver mixes CLI/env/settings layers, which is fine for single-provider setups but cumbersome when frequently switching. Define provider catalogs whenever multi-model workflows are common so that switches stay atomic, source-attributed, and debuggable.
|
- Without `modelProviders`, the resolver mixes CLI/env/settings layers, which is fine for single-provider setups but cumbersome when frequently switching. Define provider catalogs whenever multi-model workflows are common so that switches stay atomic, source-attributed, and debuggable.
|
||||||
|
|
||||||
#### context
|
#### context
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
import { AuthDialog } from './AuthDialog.js';
|
import { AuthDialog } from './AuthDialog.js';
|
||||||
import { LoadedSettings, SettingScope } from '../../config/settings.js';
|
import { LoadedSettings } from '../../config/settings.js';
|
||||||
import { AuthType } from '@qwen-code/qwen-code-core';
|
import { AuthType } from '@qwen-code/qwen-code-core';
|
||||||
import { renderWithProviders } from '../../test-utils/render.js';
|
import { renderWithProviders } from '../../test-utils/render.js';
|
||||||
import { UIStateContext } from '../contexts/UIStateContext.js';
|
import { UIStateContext } from '../contexts/UIStateContext.js';
|
||||||
|
|
@ -536,7 +536,7 @@ describe('AuthDialog', () => {
|
||||||
await wait();
|
await wait();
|
||||||
|
|
||||||
// Should call handleAuthSelect with undefined to exit
|
// Should call handleAuthSelect with undefined to exit
|
||||||
expect(handleAuthSelect).toHaveBeenCalledWith(undefined, SettingScope.User);
|
expect(handleAuthSelect).toHaveBeenCalledWith(undefined);
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import type React from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { AuthType } from '@qwen-code/qwen-code-core';
|
import { AuthType } from '@qwen-code/qwen-code-core';
|
||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { SettingScope } from '../../config/settings.js';
|
|
||||||
import { Colors } from '../colors.js';
|
import { Colors } from '../colors.js';
|
||||||
import { useKeypress } from '../hooks/useKeypress.js';
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
|
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
|
||||||
|
|
@ -84,7 +83,7 @@ export function AuthDialog(): React.JSX.Element {
|
||||||
|
|
||||||
const handleAuthSelect = async (authMethod: AuthType) => {
|
const handleAuthSelect = async (authMethod: AuthType) => {
|
||||||
setErrorMessage(null);
|
setErrorMessage(null);
|
||||||
await onAuthSelect(authMethod, SettingScope.User);
|
await onAuthSelect(authMethod);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHighlight = (authMethod: AuthType) => {
|
const handleHighlight = (authMethod: AuthType) => {
|
||||||
|
|
@ -109,7 +108,7 @@ export function AuthDialog(): React.JSX.Element {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onAuthSelect(undefined, SettingScope.User);
|
onAuthSelect(undefined);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ isActive: true },
|
{ isActive: true },
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ import {
|
||||||
logAuth,
|
logAuth,
|
||||||
} from '@qwen-code/qwen-code-core';
|
} from '@qwen-code/qwen-code-core';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import type { LoadedSettings, SettingScope } from '../../config/settings.js';
|
import type { LoadedSettings } from '../../config/settings.js';
|
||||||
|
import { getPersistScopeForModelSelection } from '../../config/modelProvidersScope.js';
|
||||||
import type { OpenAICredentials } from '../components/OpenAIKeyPrompt.js';
|
import type { OpenAICredentials } from '../components/OpenAIKeyPrompt.js';
|
||||||
import { useQwenAuth } from '../hooks/useQwenAuth.js';
|
import { useQwenAuth } from '../hooks/useQwenAuth.js';
|
||||||
import { AuthState, MessageType } from '../types.js';
|
import { AuthState, MessageType } from '../types.js';
|
||||||
|
|
@ -80,33 +81,34 @@ export const useAuthCommand = (
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAuthSuccess = useCallback(
|
const handleAuthSuccess = useCallback(
|
||||||
async (
|
async (authType: AuthType, credentials?: OpenAICredentials) => {
|
||||||
authType: AuthType,
|
|
||||||
scope: SettingScope,
|
|
||||||
credentials?: OpenAICredentials,
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
settings.setValue(scope, 'security.auth.selectedType', authType);
|
const authTypeScope = getPersistScopeForModelSelection(settings);
|
||||||
|
settings.setValue(
|
||||||
|
authTypeScope,
|
||||||
|
'security.auth.selectedType',
|
||||||
|
authType,
|
||||||
|
);
|
||||||
|
|
||||||
// Only update credentials if not switching to QWEN_OAUTH,
|
// Only update credentials if not switching to QWEN_OAUTH,
|
||||||
// so that OpenAI credentials are preserved when switching to QWEN_OAUTH.
|
// so that OpenAI credentials are preserved when switching to QWEN_OAUTH.
|
||||||
if (authType !== AuthType.QWEN_OAUTH && credentials) {
|
if (authType !== AuthType.QWEN_OAUTH && credentials) {
|
||||||
if (credentials?.apiKey != null) {
|
if (credentials?.apiKey != null) {
|
||||||
settings.setValue(
|
settings.setValue(
|
||||||
scope,
|
authTypeScope,
|
||||||
'security.auth.apiKey',
|
'security.auth.apiKey',
|
||||||
credentials.apiKey,
|
credentials.apiKey,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (credentials?.baseUrl != null) {
|
if (credentials?.baseUrl != null) {
|
||||||
settings.setValue(
|
settings.setValue(
|
||||||
scope,
|
authTypeScope,
|
||||||
'security.auth.baseUrl',
|
'security.auth.baseUrl',
|
||||||
credentials.baseUrl,
|
credentials.baseUrl,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (credentials?.model != null) {
|
if (credentials?.model != null) {
|
||||||
settings.setValue(scope, 'model.name', credentials.model);
|
settings.setValue(authTypeScope, 'model.name', credentials.model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -139,14 +141,10 @@ export const useAuthCommand = (
|
||||||
);
|
);
|
||||||
|
|
||||||
const performAuth = useCallback(
|
const performAuth = useCallback(
|
||||||
async (
|
async (authType: AuthType, credentials?: OpenAICredentials) => {
|
||||||
authType: AuthType,
|
|
||||||
scope: SettingScope,
|
|
||||||
credentials?: OpenAICredentials,
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
await config.refreshAuth(authType);
|
await config.refreshAuth(authType);
|
||||||
handleAuthSuccess(authType, scope, credentials);
|
handleAuthSuccess(authType, credentials);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleAuthFailure(e);
|
handleAuthFailure(e);
|
||||||
}
|
}
|
||||||
|
|
@ -155,11 +153,7 @@ export const useAuthCommand = (
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAuthSelect = useCallback(
|
const handleAuthSelect = useCallback(
|
||||||
async (
|
async (authType: AuthType | undefined, credentials?: OpenAICredentials) => {
|
||||||
authType: AuthType | undefined,
|
|
||||||
scope: SettingScope,
|
|
||||||
credentials?: OpenAICredentials,
|
|
||||||
) => {
|
|
||||||
if (!authType) {
|
if (!authType) {
|
||||||
setIsAuthDialogOpen(false);
|
setIsAuthDialogOpen(false);
|
||||||
setAuthError(null);
|
setAuthError(null);
|
||||||
|
|
@ -178,12 +172,12 @@ export const useAuthCommand = (
|
||||||
baseUrl: credentials.baseUrl,
|
baseUrl: credentials.baseUrl,
|
||||||
model: credentials.model,
|
model: credentials.model,
|
||||||
});
|
});
|
||||||
await performAuth(authType, scope, credentials);
|
await performAuth(authType, credentials);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await performAuth(authType, scope);
|
await performAuth(authType);
|
||||||
},
|
},
|
||||||
[config, performAuth],
|
[config, performAuth],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import { useUIState } from '../contexts/UIStateContext.js';
|
||||||
import { useUIActions } from '../contexts/UIActionsContext.js';
|
import { useUIActions } from '../contexts/UIActionsContext.js';
|
||||||
import { useConfig } from '../contexts/ConfigContext.js';
|
import { useConfig } from '../contexts/ConfigContext.js';
|
||||||
import { useSettings } from '../contexts/SettingsContext.js';
|
import { useSettings } from '../contexts/SettingsContext.js';
|
||||||
import { SettingScope } from '../../config/settings.js';
|
|
||||||
import { AuthState } from '../types.js';
|
import { AuthState } from '../types.js';
|
||||||
import { AuthType } from '@qwen-code/qwen-code-core';
|
import { AuthType } from '@qwen-code/qwen-code-core';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
|
|
@ -202,7 +201,7 @@ export const DialogManager = ({
|
||||||
return (
|
return (
|
||||||
<OpenAIKeyPrompt
|
<OpenAIKeyPrompt
|
||||||
onSubmit={(apiKey, baseUrl, model) => {
|
onSubmit={(apiKey, baseUrl, model) => {
|
||||||
uiActions.handleAuthSelect(AuthType.USE_OPENAI, SettingScope.User, {
|
uiActions.handleAuthSelect(AuthType.USE_OPENAI, {
|
||||||
apiKey,
|
apiKey,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
model,
|
model,
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ export interface UIActions {
|
||||||
) => void;
|
) => void;
|
||||||
handleAuthSelect: (
|
handleAuthSelect: (
|
||||||
authType: AuthType | undefined,
|
authType: AuthType | undefined,
|
||||||
scope: SettingScope,
|
|
||||||
credentials?: OpenAICredentials,
|
credentials?: OpenAICredentials,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
setAuthState: (state: AuthState) => void;
|
setAuthState: (state: AuthState) => void;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ export interface DialogCloseOptions {
|
||||||
isAuthDialogOpen: boolean;
|
isAuthDialogOpen: boolean;
|
||||||
handleAuthSelect: (
|
handleAuthSelect: (
|
||||||
authType: AuthType | undefined,
|
authType: AuthType | undefined,
|
||||||
scope: SettingScope,
|
|
||||||
credentials?: OpenAICredentials,
|
credentials?: OpenAICredentials,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
pendingAuthType: AuthType | undefined;
|
pendingAuthType: AuthType | undefined;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue