mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat(auth): 优化认证流程并添加认证状态管理
- 新增 AuthStateManager 类用于管理认证状态 - 修改 createNewSession 方法以使用缓存的认证信息 - 添加清除认证缓存的功能 - 优化登录命令处理,增加加载状态显示 - 新增登录成功和失败的消息处理
This commit is contained in:
@@ -429,21 +429,60 @@ export class QwenAgentManager {
|
|||||||
* @param workingDir - Working directory
|
* @param workingDir - Working directory
|
||||||
* @returns Newly created session ID
|
* @returns Newly created session ID
|
||||||
*/
|
*/
|
||||||
async createNewSession(workingDir: string): Promise<string | null> {
|
async createNewSession(
|
||||||
|
workingDir: string,
|
||||||
|
authStateManager?: AuthStateManager,
|
||||||
|
): Promise<string | null> {
|
||||||
console.log('[QwenAgentManager] Creating new session...');
|
console.log('[QwenAgentManager] Creating new session...');
|
||||||
|
|
||||||
// Authenticate first
|
// Check if we have valid cached authentication
|
||||||
console.log('[QwenAgentManager] Authenticating before creating session...');
|
let hasValidAuth = false;
|
||||||
try {
|
const config = vscode.workspace.getConfiguration('qwenCode');
|
||||||
const config = vscode.workspace.getConfiguration('qwenCode');
|
const openaiApiKey = config.get<string>('qwen.openaiApiKey', '');
|
||||||
const openaiApiKey = config.get<string>('qwen.openaiApiKey', '');
|
const authMethod = openaiApiKey ? 'openai' : 'qwen-oauth';
|
||||||
const authMethod = openaiApiKey ? 'openai' : 'qwen-oauth';
|
|
||||||
|
|
||||||
await this.connection.authenticate(authMethod);
|
if (authStateManager) {
|
||||||
console.log('[QwenAgentManager] Authentication successful');
|
hasValidAuth = await authStateManager.hasValidAuth(
|
||||||
} catch (authError) {
|
workingDir,
|
||||||
console.error('[QwenAgentManager] Authentication failed:', authError);
|
authMethod,
|
||||||
throw authError;
|
);
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Has valid cached auth for new session:',
|
||||||
|
hasValidAuth,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only authenticate if we don't have valid cached auth
|
||||||
|
if (!hasValidAuth) {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Authenticating before creating session...',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await this.connection.authenticate(authMethod);
|
||||||
|
console.log('[QwenAgentManager] Authentication successful');
|
||||||
|
|
||||||
|
// Save auth state
|
||||||
|
if (authStateManager) {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Saving auth state after successful authentication',
|
||||||
|
);
|
||||||
|
await authStateManager.saveAuthState(workingDir, authMethod);
|
||||||
|
}
|
||||||
|
} catch (authError) {
|
||||||
|
console.error('[QwenAgentManager] Authentication failed:', authError);
|
||||||
|
// Clear potentially invalid cache
|
||||||
|
if (authStateManager) {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Clearing auth cache due to authentication failure',
|
||||||
|
);
|
||||||
|
await authStateManager.clearAuthState();
|
||||||
|
}
|
||||||
|
throw authError;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
'[QwenAgentManager] Skipping authentication - using valid cached auth',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.connection.newSession(workingDir);
|
await this.connection.newSession(workingDir);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
type IdeInfo,
|
type IdeInfo,
|
||||||
} from '@qwen-code/qwen-code-core/src/ide/detect-ide.js';
|
} from '@qwen-code/qwen-code-core/src/ide/detect-ide.js';
|
||||||
import { WebViewProvider } from './webview/WebViewProvider.js';
|
import { WebViewProvider } from './webview/WebViewProvider.js';
|
||||||
import { AuthStateManager } from './auth/authStateManager.js';
|
|
||||||
|
|
||||||
const CLI_IDE_COMPANION_IDENTIFIER = 'qwenlm.qwen-code-vscode-ide-companion';
|
const CLI_IDE_COMPANION_IDENTIFIER = 'qwenlm.qwen-code-vscode-ide-companion';
|
||||||
const INFO_MESSAGE_SHOWN_KEY = 'qwenCodeInfoMessageShown';
|
const INFO_MESSAGE_SHOWN_KEY = 'qwenCodeInfoMessageShown';
|
||||||
@@ -34,7 +33,6 @@ const HIDE_INSTALLATION_GREETING_IDES: ReadonlySet<IdeInfo['name']> = new Set([
|
|||||||
let ideServer: IDEServer;
|
let ideServer: IDEServer;
|
||||||
let logger: vscode.OutputChannel;
|
let logger: vscode.OutputChannel;
|
||||||
let webViewProviders: WebViewProvider[] = []; // Track multiple chat tabs
|
let webViewProviders: WebViewProvider[] = []; // Track multiple chat tabs
|
||||||
let authStateManager: AuthStateManager;
|
|
||||||
|
|
||||||
let log: (message: string) => void = () => {};
|
let log: (message: string) => void = () => {};
|
||||||
|
|
||||||
@@ -114,30 +112,10 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
const diffContentProvider = new DiffContentProvider();
|
const diffContentProvider = new DiffContentProvider();
|
||||||
const diffManager = new DiffManager(log, diffContentProvider);
|
const diffManager = new DiffManager(log, diffContentProvider);
|
||||||
|
|
||||||
// Initialize Auth State Manager
|
|
||||||
console.log('[Extension] Initializing global AuthStateManager');
|
|
||||||
authStateManager = new AuthStateManager(context);
|
|
||||||
console.log(
|
|
||||||
'[Extension] Global AuthStateManager initialized:',
|
|
||||||
!!authStateManager,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Helper function to create a new WebView provider instance
|
// Helper function to create a new WebView provider instance
|
||||||
const createWebViewProvider = (): WebViewProvider => {
|
const createWebViewProvider = (): WebViewProvider => {
|
||||||
console.log(
|
const provider = new WebViewProvider(context, context.extensionUri);
|
||||||
'[Extension] Creating WebViewProvider with global AuthStateManager:',
|
|
||||||
!!authStateManager,
|
|
||||||
);
|
|
||||||
const provider = new WebViewProvider(
|
|
||||||
context,
|
|
||||||
context.extensionUri,
|
|
||||||
authStateManager,
|
|
||||||
);
|
|
||||||
webViewProviders.push(provider);
|
webViewProviders.push(provider);
|
||||||
console.log(
|
|
||||||
'[Extension] WebViewProvider created, total providers:',
|
|
||||||
webViewProviders.length,
|
|
||||||
);
|
|
||||||
return provider;
|
return provider;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -243,12 +221,10 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
provider.show();
|
provider.show();
|
||||||
}),
|
}),
|
||||||
vscode.commands.registerCommand('qwenCode.clearAuthCache', async () => {
|
vscode.commands.registerCommand('qwenCode.clearAuthCache', async () => {
|
||||||
await authStateManager.clearAuthState();
|
// Clear auth state for all WebView providers
|
||||||
|
for (const provider of webViewProviders) {
|
||||||
// Reset all WebView agent states to force re-authentication
|
await provider.clearAuthCache();
|
||||||
webViewProviders.forEach((provider) => {
|
}
|
||||||
provider.resetAgentState();
|
|
||||||
});
|
|
||||||
|
|
||||||
vscode.window.showInformationMessage(
|
vscode.window.showInformationMessage(
|
||||||
'Qwen Code authentication cache cleared. You will need to login again on next connection.',
|
'Qwen Code authentication cache cleared. You will need to login again on next connection.',
|
||||||
|
|||||||
@@ -28,12 +28,10 @@ export class WebViewProvider {
|
|||||||
constructor(
|
constructor(
|
||||||
context: vscode.ExtensionContext,
|
context: vscode.ExtensionContext,
|
||||||
private extensionUri: vscode.Uri,
|
private extensionUri: vscode.Uri,
|
||||||
authStateManager?: AuthStateManager, // Optional global AuthStateManager instance
|
|
||||||
) {
|
) {
|
||||||
this.agentManager = new QwenAgentManager();
|
this.agentManager = new QwenAgentManager();
|
||||||
this.conversationStore = new ConversationStore(context);
|
this.conversationStore = new ConversationStore(context);
|
||||||
// If a global authStateManager is provided, use it, otherwise create a new instance
|
this.authStateManager = new AuthStateManager(context);
|
||||||
this.authStateManager = authStateManager || new AuthStateManager(context);
|
|
||||||
this.panelManager = new PanelManager(extensionUri, () => {
|
this.panelManager = new PanelManager(extensionUri, () => {
|
||||||
// Panel dispose callback
|
// Panel dispose callback
|
||||||
this.disposables.forEach((d) => d.dispose());
|
this.disposables.forEach((d) => d.dispose());
|
||||||
@@ -527,7 +525,10 @@ export class WebViewProvider {
|
|||||||
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
|
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.agentManager.createNewSession(workingDir);
|
await this.agentManager.createNewSession(
|
||||||
|
workingDir,
|
||||||
|
this.authStateManager,
|
||||||
|
);
|
||||||
console.log('[WebViewProvider] ACP session created successfully');
|
console.log('[WebViewProvider] ACP session created successfully');
|
||||||
} catch (sessionError) {
|
} catch (sessionError) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -601,6 +602,17 @@ export class WebViewProvider {
|
|||||||
this.agentManager.disconnect();
|
this.agentManager.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear authentication cache for this WebViewProvider instance
|
||||||
|
*/
|
||||||
|
async clearAuthCache(): Promise<void> {
|
||||||
|
console.log('[WebViewProvider] Clearing auth cache for this instance');
|
||||||
|
if (this.authStateManager) {
|
||||||
|
await this.authStateManager.clearAuthState();
|
||||||
|
this.resetAgentState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore an existing WebView panel (called during VSCode restart)
|
* Restore an existing WebView panel (called during VSCode restart)
|
||||||
* This sets up the panel with all event listeners
|
* This sets up the panel with all event listeners
|
||||||
@@ -818,7 +830,10 @@ export class WebViewProvider {
|
|||||||
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
|
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
|
||||||
|
|
||||||
// Create new Qwen session via agent manager
|
// Create new Qwen session via agent manager
|
||||||
await this.agentManager.createNewSession(workingDir);
|
await this.agentManager.createNewSession(
|
||||||
|
workingDir,
|
||||||
|
this.authStateManager,
|
||||||
|
);
|
||||||
|
|
||||||
// Clear current conversation UI
|
// Clear current conversation UI
|
||||||
this.sendMessageToWebView({
|
this.sendMessageToWebView({
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const useMessageSubmit = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle /login command
|
// Handle /login command - show inline loading while extension authenticates
|
||||||
if (inputText.trim() === '/login') {
|
if (inputText.trim() === '/login') {
|
||||||
setInputText('');
|
setInputText('');
|
||||||
if (inputFieldRef.current) {
|
if (inputFieldRef.current) {
|
||||||
@@ -59,6 +59,12 @@ export const useMessageSubmit = ({
|
|||||||
type: 'login',
|
type: 'login',
|
||||||
data: {},
|
data: {},
|
||||||
});
|
});
|
||||||
|
// Show a friendly loading message in the chat while logging in
|
||||||
|
try {
|
||||||
|
messageHandling.setWaitingForResponse('Logging in to Qwen Code...');
|
||||||
|
} catch (_err) {
|
||||||
|
// Best-effort UI hint; ignore if hook not available
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,31 @@ export const useWebViewMessages = ({
|
|||||||
const handlers = handlersRef.current;
|
const handlers = handlersRef.current;
|
||||||
|
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
|
case 'loginSuccess': {
|
||||||
|
// Clear loading state and show a short assistant notice
|
||||||
|
handlers.messageHandling.clearWaitingForResponse();
|
||||||
|
handlers.messageHandling.addMessage({
|
||||||
|
role: 'assistant',
|
||||||
|
content: 'Successfully logged in. You can continue chatting.',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'loginError': {
|
||||||
|
// Clear loading state and show error notice
|
||||||
|
handlers.messageHandling.clearWaitingForResponse();
|
||||||
|
const errorMsg =
|
||||||
|
(message?.data?.message as string) ||
|
||||||
|
'Login failed. Please try again.';
|
||||||
|
handlers.messageHandling.addMessage({
|
||||||
|
role: 'assistant',
|
||||||
|
content: errorMsg,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'conversationLoaded': {
|
case 'conversationLoaded': {
|
||||||
const conversation = message.data as Conversation;
|
const conversation = message.data as Conversation;
|
||||||
handlers.messageHandling.setMessages(conversation.messages);
|
handlers.messageHandling.setMessages(conversation.messages);
|
||||||
|
|||||||
Reference in New Issue
Block a user