mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
refactor(vscode-ide-companion/cli): consolidate CLI detection and version management
- Replace separate CliDetector, CliVersionChecker, and CliVersionManager classes with unified CliManager - Remove redundant code and simplify CLI detection logic - Maintain all existing functionality while improving code organization - Update imports in dependent files to use CliManager This change reduces complexity by consolidating CLI-related functionality into a single manager class.
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { CliFeatureFlags, CliVersionInfo } from './cliVersionManager.js';
|
||||
import type { CliFeatureFlags, CliVersionInfo } from './cliManager.js';
|
||||
|
||||
export class CliContextManager {
|
||||
private static instance: CliContextManager;
|
||||
|
||||
@@ -1,332 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
export interface CliDetectionResult {
|
||||
isInstalled: boolean;
|
||||
cliPath?: string;
|
||||
version?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if Qwen Code CLI is installed and accessible
|
||||
*/
|
||||
export class CliDetector {
|
||||
private static cachedResult: CliDetectionResult | null = null;
|
||||
private static lastCheckTime: number = 0;
|
||||
private static readonly CACHE_DURATION_MS = 30000; // 30 seconds
|
||||
|
||||
/**
|
||||
* Lightweight CLI Detection Method
|
||||
*
|
||||
* This method is designed for performance optimization, checking only if the CLI exists
|
||||
* without retrieving version information.
|
||||
* Suitable for quick detection scenarios, such as pre-checks before initializing connections.
|
||||
*
|
||||
* Compared to the full detectQwenCli method, this method:
|
||||
* - Omits version information retrieval step
|
||||
* - Uses shorter timeout (3 seconds)
|
||||
* - Faster response time
|
||||
*
|
||||
* @param forceRefresh - Whether to force refresh cached results, default is false
|
||||
* @returns Promise<CliDetectionResult> - Detection result containing installation status and path
|
||||
*/
|
||||
static async detectQwenCliLightweight(
|
||||
forceRefresh = false,
|
||||
): Promise<CliDetectionResult> {
|
||||
const now = Date.now();
|
||||
|
||||
// Check if cached result is available and not expired (30-second validity)
|
||||
if (
|
||||
!forceRefresh &&
|
||||
this.cachedResult &&
|
||||
now - this.lastCheckTime < this.CACHE_DURATION_MS
|
||||
) {
|
||||
return this.cachedResult;
|
||||
}
|
||||
|
||||
try {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const whichCommand = isWindows ? 'where' : 'which';
|
||||
|
||||
// Check if qwen command exists
|
||||
try {
|
||||
// Use simplified detection without NVM for speed
|
||||
const detectionCommand = isWindows
|
||||
? `${whichCommand} qwen`
|
||||
: `${whichCommand} qwen`;
|
||||
|
||||
// Execute command to detect CLI path, set shorter timeout (3 seconds)
|
||||
const { stdout } = await execAsync(detectionCommand, {
|
||||
timeout: 3000, // Reduced timeout for faster detection
|
||||
shell: isWindows ? undefined : '/bin/bash',
|
||||
});
|
||||
|
||||
// Output may contain multiple lines, get first line as actual path
|
||||
const lines = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((line) => line.trim());
|
||||
const cliPath = lines[0]; // Take only the first path
|
||||
|
||||
// Build successful detection result, note no version information
|
||||
this.cachedResult = {
|
||||
isInstalled: true,
|
||||
cliPath,
|
||||
// Version information not retrieved in lightweight detection
|
||||
};
|
||||
this.lastCheckTime = now;
|
||||
return this.cachedResult;
|
||||
} catch (detectionError) {
|
||||
console.log('[CliDetector] CLI not found, error:', detectionError);
|
||||
|
||||
// CLI not found, build error message
|
||||
let error = `Qwen Code CLI not found in PATH. Please install using: npm install -g @qwen-code/qwen-code@latest`;
|
||||
|
||||
// Provide specific guidance for permission errors
|
||||
if (detectionError instanceof Error) {
|
||||
const errorMessage = detectionError.message;
|
||||
if (
|
||||
errorMessage.includes('EACCES') ||
|
||||
errorMessage.includes('Permission denied')
|
||||
) {
|
||||
error += `\n\nThis may be due to permission issues. Solutions:
|
||||
\n1. Reinstall CLI without sudo: npm install -g @qwen-code/qwen-code@latest
|
||||
\n2. If previously installed with sudo, fix ownership: sudo chown -R $(whoami) $(npm config get prefix)/lib/node_modules/@qwen-code/qwen-code
|
||||
\n3. Use nvm for Node.js version management to avoid permission issues
|
||||
\n4. Check PATH environment variable includes npm's global bin directory`;
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedResult = {
|
||||
isInstalled: false,
|
||||
error,
|
||||
};
|
||||
this.lastCheckTime = now;
|
||||
return this.cachedResult;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('[CliDetector] General detection error:', error);
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
let userFriendlyError = `Failed to detect Qwen Code CLI: ${errorMessage}`;
|
||||
|
||||
// Provide specific guidance for permission errors
|
||||
if (
|
||||
errorMessage.includes('EACCES') ||
|
||||
errorMessage.includes('Permission denied')
|
||||
) {
|
||||
userFriendlyError += `\n\nThis may be due to permission issues. Solutions:
|
||||
\n1. Reinstall CLI without sudo: npm install -g @qwen-code/qwen-code@latest
|
||||
\n2. If previously installed with sudo, fix ownership: sudo chown -R $(whoami) $(npm config get prefix)/lib/node_modules/@qwen-code/qwen-code
|
||||
\n3. Use nvm for Node.js version management to avoid permission issues
|
||||
\n4. Check PATH environment variable includes npm's global bin directory`;
|
||||
}
|
||||
|
||||
this.cachedResult = {
|
||||
isInstalled: false,
|
||||
error: userFriendlyError,
|
||||
};
|
||||
this.lastCheckTime = now;
|
||||
return this.cachedResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Qwen Code CLI is installed
|
||||
* @param forceRefresh - Force a new check, ignoring cache
|
||||
* @returns Detection result with installation status and details
|
||||
*/
|
||||
static async detectQwenCli(
|
||||
forceRefresh = false,
|
||||
): Promise<CliDetectionResult> {
|
||||
const now = Date.now();
|
||||
|
||||
// Return cached result if available and not expired
|
||||
if (
|
||||
!forceRefresh &&
|
||||
this.cachedResult &&
|
||||
now - this.lastCheckTime < this.CACHE_DURATION_MS
|
||||
) {
|
||||
console.log('[CliDetector] Returning cached result');
|
||||
return this.cachedResult;
|
||||
}
|
||||
|
||||
console.log(
|
||||
'[CliDetector] Starting CLI detection, current PATH:',
|
||||
process.env.PATH,
|
||||
);
|
||||
|
||||
try {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const whichCommand = isWindows ? 'where' : 'which';
|
||||
|
||||
// Check if qwen command exists
|
||||
try {
|
||||
// Use NVM environment for consistent detection
|
||||
// Fallback chain: default alias -> node alias -> current version
|
||||
const detectionCommand =
|
||||
process.platform === 'win32'
|
||||
? `${whichCommand} qwen`
|
||||
: 'source ~/.nvm/nvm.sh 2>/dev/null && (nvm use default 2>/dev/null || nvm use node 2>/dev/null || nvm use 2>/dev/null); which qwen';
|
||||
|
||||
console.log(
|
||||
'[CliDetector] Detecting CLI with command:',
|
||||
detectionCommand,
|
||||
);
|
||||
|
||||
const { stdout } = await execAsync(detectionCommand, {
|
||||
timeout: 5000,
|
||||
shell: isWindows ? undefined : '/bin/bash',
|
||||
});
|
||||
// The output may contain multiple lines, with NVM activation messages
|
||||
// We want the last line which should be the actual path
|
||||
const lines = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((line) => line.trim());
|
||||
const cliPath = lines[lines.length - 1];
|
||||
|
||||
console.log('[CliDetector] Found CLI at:', cliPath);
|
||||
|
||||
// Try to get version
|
||||
let version: string | undefined;
|
||||
try {
|
||||
// Use NVM environment for version check
|
||||
// Fallback chain: default alias -> node alias -> current version
|
||||
// Also ensure we use the correct Node.js version that matches the CLI installation
|
||||
const versionCommand =
|
||||
process.platform === 'win32'
|
||||
? 'qwen --version'
|
||||
: 'source ~/.nvm/nvm.sh 2>/dev/null && (nvm use default 2>/dev/null || nvm use node 2>/dev/null || nvm use 2>/dev/null); qwen --version';
|
||||
|
||||
console.log(
|
||||
'[CliDetector] Getting version with command:',
|
||||
versionCommand,
|
||||
);
|
||||
|
||||
const { stdout: versionOutput } = await execAsync(versionCommand, {
|
||||
timeout: 5000,
|
||||
shell: isWindows ? undefined : '/bin/bash',
|
||||
});
|
||||
// The output may contain multiple lines, with NVM activation messages
|
||||
// We want the last line which should be the actual version
|
||||
const versionLines = versionOutput
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((line) => line.trim());
|
||||
version = versionLines[versionLines.length - 1];
|
||||
console.log('[CliDetector] CLI version:', version);
|
||||
} catch (versionError) {
|
||||
console.log('[CliDetector] Failed to get CLI version:', versionError);
|
||||
// Version check failed, but CLI is installed
|
||||
}
|
||||
|
||||
this.cachedResult = {
|
||||
isInstalled: true,
|
||||
cliPath,
|
||||
version,
|
||||
};
|
||||
this.lastCheckTime = now;
|
||||
return this.cachedResult;
|
||||
} catch (detectionError) {
|
||||
console.log('[CliDetector] CLI not found, error:', detectionError);
|
||||
// CLI not found
|
||||
let error = `Qwen Code CLI not found in PATH. Please install it using: npm install -g @qwen-code/qwen-code@latest`;
|
||||
|
||||
// Provide specific guidance for permission errors
|
||||
if (detectionError instanceof Error) {
|
||||
const errorMessage = detectionError.message;
|
||||
if (
|
||||
errorMessage.includes('EACCES') ||
|
||||
errorMessage.includes('Permission denied')
|
||||
) {
|
||||
error += `\n\nThis may be due to permission issues. Possible solutions:
|
||||
\n1. Reinstall the CLI without sudo: npm install -g @qwen-code/qwen-code@latest
|
||||
\n2. If previously installed with sudo, fix ownership: sudo chown -R $(whoami) $(npm config get prefix)/lib/node_modules/@qwen-code/qwen-code
|
||||
\n3. Use nvm for Node.js version management to avoid permission issues
|
||||
\n4. Check your PATH environment variable includes npm's global bin directory`;
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedResult = {
|
||||
isInstalled: false,
|
||||
error,
|
||||
};
|
||||
this.lastCheckTime = now;
|
||||
return this.cachedResult;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('[CliDetector] General detection error:', error);
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
let userFriendlyError = `Failed to detect Qwen Code CLI: ${errorMessage}`;
|
||||
|
||||
// Provide specific guidance for permission errors
|
||||
if (
|
||||
errorMessage.includes('EACCES') ||
|
||||
errorMessage.includes('Permission denied')
|
||||
) {
|
||||
userFriendlyError += `\n\nThis may be due to permission issues. Possible solutions:
|
||||
\n1. Reinstall the CLI without sudo: npm install -g @qwen-code/qwen-code@latest
|
||||
\n2. If previously installed with sudo, fix ownership: sudo chown -R $(whoami) $(npm config get prefix)/lib/node_modules/@qwen-code/qwen-code
|
||||
\n3. Use nvm for Node.js version management to avoid permission issues
|
||||
\n4. Check your PATH environment variable includes npm's global bin directory`;
|
||||
}
|
||||
|
||||
this.cachedResult = {
|
||||
isInstalled: false,
|
||||
error: userFriendlyError,
|
||||
};
|
||||
this.lastCheckTime = now;
|
||||
return this.cachedResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cached detection result
|
||||
*/
|
||||
static clearCache(): void {
|
||||
this.cachedResult = null;
|
||||
this.lastCheckTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets installation instructions based on the platform
|
||||
*/
|
||||
static getInstallationInstructions(): {
|
||||
title: string;
|
||||
steps: string[];
|
||||
documentationUrl: string;
|
||||
} {
|
||||
return {
|
||||
title: 'Qwen Code CLI is not installed',
|
||||
steps: [
|
||||
'Install via npm:',
|
||||
' npm install -g @qwen-code/qwen-code@latest',
|
||||
'',
|
||||
'If you are using nvm (automatically handled by the plugin):',
|
||||
' The plugin will automatically use your default nvm version',
|
||||
'',
|
||||
'Or install from source:',
|
||||
' git clone https://github.com/QwenLM/qwen-code.git',
|
||||
' cd qwen-code',
|
||||
' npm install',
|
||||
' npm install -g .',
|
||||
'',
|
||||
'After installation, reload VS Code or restart the extension.',
|
||||
],
|
||||
documentationUrl: 'https://github.com/QwenLM/qwen-code#installation',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { CliDetector } from './cliDetector.js';
|
||||
import { CliManager } from './cliManager.js';
|
||||
|
||||
/**
|
||||
* CLI Detection and Installation Handler
|
||||
@@ -20,7 +20,7 @@ export class CliInstaller {
|
||||
sendToWebView: (message: unknown) => void,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const result = await CliDetector.detectQwenCli();
|
||||
const result = await CliManager.detectQwenCli();
|
||||
|
||||
sendToWebView({
|
||||
type: 'cliDetectionResult',
|
||||
@@ -31,7 +31,7 @@ export class CliInstaller {
|
||||
error: result.error,
|
||||
installInstructions: result.isInstalled
|
||||
? undefined
|
||||
: CliDetector.getInstallationInstructions(),
|
||||
: CliManager.getInstallationInstructions(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -134,8 +134,8 @@ export class CliInstaller {
|
||||
}
|
||||
|
||||
// Clear cache and recheck
|
||||
CliDetector.clearCache();
|
||||
const detection = await CliDetector.detectQwenCli();
|
||||
CliManager.clearCache();
|
||||
const detection = await CliManager.detectQwenCli();
|
||||
|
||||
if (detection.isInstalled) {
|
||||
vscode.window
|
||||
|
||||
498
packages/vscode-ide-companion/src/cli/cliManager.ts
Normal file
498
packages/vscode-ide-companion/src/cli/cliManager.ts
Normal file
@@ -0,0 +1,498 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import semver from 'semver';
|
||||
import { CliInstaller } from './cliInstaller.js';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
export const MIN_CLI_VERSION_FOR_SESSION_METHODS = '0.5.0';
|
||||
|
||||
export interface CliDetectionResult {
|
||||
isInstalled: boolean;
|
||||
cliPath?: string;
|
||||
version?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface CliFeatureFlags {
|
||||
supportsSessionList: boolean;
|
||||
supportsSessionLoad: boolean;
|
||||
}
|
||||
|
||||
export interface CliVersionInfo {
|
||||
version: string | undefined;
|
||||
isSupported: boolean;
|
||||
features: CliFeatureFlags;
|
||||
detectionResult: CliDetectionResult;
|
||||
}
|
||||
|
||||
export class CliManager {
|
||||
private static instance: CliManager;
|
||||
private lastNotificationTime: number = 0;
|
||||
private static readonly NOTIFICATION_COOLDOWN_MS = 300000; // 5 minutes cooldown
|
||||
private context: vscode.ExtensionContext | undefined;
|
||||
|
||||
// Cache mechanisms
|
||||
private static cachedDetectionResult: CliDetectionResult | null = null;
|
||||
private static detectionLastCheckTime: number = 0;
|
||||
private cachedVersionInfo: CliVersionInfo | null = null;
|
||||
private versionLastCheckTime: number = 0;
|
||||
private static readonly CACHE_DURATION_MS = 30000; // 30 seconds
|
||||
|
||||
private constructor(context?: vscode.ExtensionContext) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*/
|
||||
static getInstance(context?: vscode.ExtensionContext): CliManager {
|
||||
if (!CliManager.instance && context) {
|
||||
CliManager.instance = new CliManager(context);
|
||||
}
|
||||
return CliManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Qwen Code CLI is installed
|
||||
* @param forceRefresh - Force a new check, ignoring cache
|
||||
* @returns Detection result with installation status and details
|
||||
*/
|
||||
static async detectQwenCli(
|
||||
forceRefresh = false,
|
||||
): Promise<CliDetectionResult> {
|
||||
const now = Date.now();
|
||||
|
||||
// Return cached result if available and not expired
|
||||
if (
|
||||
!forceRefresh &&
|
||||
this.cachedDetectionResult &&
|
||||
now - this.detectionLastCheckTime < this.CACHE_DURATION_MS
|
||||
) {
|
||||
console.log('[CliManager] Returning cached detection result');
|
||||
return this.cachedDetectionResult;
|
||||
}
|
||||
|
||||
console.log(
|
||||
'[CliManager] Starting CLI detection, current PATH:',
|
||||
process.env.PATH,
|
||||
);
|
||||
|
||||
try {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const whichCommand = isWindows ? 'where' : 'which';
|
||||
|
||||
// Check if qwen command exists
|
||||
try {
|
||||
// Use NVM environment for consistent detection
|
||||
// Fallback chain: default alias -> node alias -> current version
|
||||
const detectionCommand =
|
||||
process.platform === 'win32'
|
||||
? `${whichCommand} qwen`
|
||||
: 'source ~/.nvm/nvm.sh 2>/dev/null && (nvm use default 2>/dev/null || nvm use node 2>/dev/null || nvm use 2>/dev/null); which qwen';
|
||||
|
||||
console.log(
|
||||
'[CliManager] Detecting CLI with command:',
|
||||
detectionCommand,
|
||||
);
|
||||
|
||||
const { stdout } = await execAsync(detectionCommand, {
|
||||
timeout: 5000,
|
||||
shell: isWindows ? undefined : '/bin/bash',
|
||||
});
|
||||
// The output may contain multiple lines, with NVM activation messages
|
||||
// We want the last line which should be the actual path
|
||||
const lines = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((line) => line.trim());
|
||||
const cliPath = lines[lines.length - 1];
|
||||
|
||||
console.log('[CliManager] Found CLI at:', cliPath);
|
||||
|
||||
// Try to get version
|
||||
let version: string | undefined;
|
||||
try {
|
||||
// Use NVM environment for version check
|
||||
// Fallback chain: default alias -> node alias -> current version
|
||||
// Also ensure we use the correct Node.js version that matches the CLI installation
|
||||
const versionCommand =
|
||||
process.platform === 'win32'
|
||||
? 'qwen --version'
|
||||
: 'source ~/.nvm/nvm.sh 2>/dev/null && (nvm use default 2>/dev/null || nvm use node 2>/dev/null || nvm use 2>/dev/null); qwen --version';
|
||||
|
||||
console.log(
|
||||
'[CliManager] Getting version with command:',
|
||||
versionCommand,
|
||||
);
|
||||
|
||||
const { stdout: versionOutput } = await execAsync(versionCommand, {
|
||||
timeout: 5000,
|
||||
shell: isWindows ? undefined : '/bin/bash',
|
||||
});
|
||||
// The output may contain multiple lines, with NVM activation messages
|
||||
// We want the last line which should be the actual version
|
||||
const versionLines = versionOutput
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((line) => line.trim());
|
||||
version = versionLines[versionLines.length - 1];
|
||||
console.log('[CliManager] CLI version:', version);
|
||||
} catch (versionError) {
|
||||
console.log('[CliManager] Failed to get CLI version:', versionError);
|
||||
// Version check failed, but CLI is installed
|
||||
}
|
||||
|
||||
this.cachedDetectionResult = {
|
||||
isInstalled: true,
|
||||
cliPath,
|
||||
version,
|
||||
};
|
||||
this.detectionLastCheckTime = now;
|
||||
return this.cachedDetectionResult;
|
||||
} catch (detectionError) {
|
||||
console.log('[CliManager] CLI not found, error:', detectionError);
|
||||
// CLI not found
|
||||
let error = `Qwen Code CLI not found in PATH. Please install it using: npm install -g @qwen-code/qwen-code@latest`;
|
||||
|
||||
// Provide specific guidance for permission errors
|
||||
if (detectionError instanceof Error) {
|
||||
const errorMessage = detectionError.message;
|
||||
if (
|
||||
errorMessage.includes('EACCES') ||
|
||||
errorMessage.includes('Permission denied')
|
||||
) {
|
||||
error += `\n\nThis may be due to permission issues. Possible solutions:
|
||||
\n1. Reinstall the CLI without sudo: npm install -g @qwen-code/qwen-code@latest
|
||||
\n2. If previously installed with sudo, fix ownership: sudo chown -R $(whoami) $(npm config get prefix)/lib/node_modules/@qwen-code/qwen-code
|
||||
\n3. Use nvm for Node.js version management to avoid permission issues
|
||||
\n4. Check your PATH environment variable includes npm's global bin directory`;
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedDetectionResult = {
|
||||
isInstalled: false,
|
||||
error,
|
||||
};
|
||||
this.detectionLastCheckTime = now;
|
||||
return this.cachedDetectionResult;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('[CliManager] General detection error:', error);
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
let userFriendlyError = `Failed to detect Qwen Code CLI: ${errorMessage}`;
|
||||
|
||||
// Provide specific guidance for permission errors
|
||||
if (
|
||||
errorMessage.includes('EACCES') ||
|
||||
errorMessage.includes('Permission denied')
|
||||
) {
|
||||
userFriendlyError += `\n\nThis may be due to permission issues. Possible solutions:
|
||||
\n1. Reinstall the CLI without sudo: npm install -g @qwen-code/qwen-code@latest
|
||||
\n2. If previously installed with sudo, fix ownership: sudo chown -R $(whoami) $(npm config get prefix)/lib/node_modules/@qwen-code/qwen-code
|
||||
\n3. Use nvm for Node.js version management to avoid permission issues
|
||||
\n4. Check your PATH environment variable includes npm's global bin directory`;
|
||||
}
|
||||
|
||||
this.cachedDetectionResult = {
|
||||
isInstalled: false,
|
||||
error: userFriendlyError,
|
||||
};
|
||||
this.detectionLastCheckTime = now;
|
||||
return this.cachedDetectionResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cached detection result
|
||||
*/
|
||||
static clearCache(): void {
|
||||
this.cachedDetectionResult = null;
|
||||
this.detectionLastCheckTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets installation instructions based on the platform
|
||||
*/
|
||||
static getInstallationInstructions(): {
|
||||
title: string;
|
||||
steps: string[];
|
||||
documentationUrl: string;
|
||||
} {
|
||||
return {
|
||||
title: 'Qwen Code CLI is not installed',
|
||||
steps: [
|
||||
'Install via npm:',
|
||||
' npm install -g @qwen-code/qwen-code@latest',
|
||||
'',
|
||||
'If you are using nvm (automatically handled by the plugin):',
|
||||
' The plugin will automatically use your default nvm version',
|
||||
'',
|
||||
'Or install from source:',
|
||||
' git clone https://github.com/QwenLM/qwen-code.git',
|
||||
' cd qwen-code',
|
||||
' npm install',
|
||||
' npm install -g .',
|
||||
'',
|
||||
'After installation, reload VS Code or restart the extension.',
|
||||
],
|
||||
documentationUrl: 'https://github.com/QwenLM/qwen-code#installation',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if CLI version meets minimum requirements
|
||||
*
|
||||
* @param version - Version string to check
|
||||
* @param minVersion - Minimum required version
|
||||
* @returns Whether version meets requirements
|
||||
*/
|
||||
private isVersionSupported(
|
||||
version: string | undefined,
|
||||
minVersion: string,
|
||||
): boolean {
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use semver for robust comparison (handles v-prefix, pre-release, etc.)
|
||||
const v = semver.valid(version) ?? semver.coerce(version)?.version ?? null;
|
||||
const min =
|
||||
semver.valid(minVersion) ?? semver.coerce(minVersion)?.version ?? null;
|
||||
|
||||
if (!v || !min) {
|
||||
console.warn(
|
||||
`[CliManager] Invalid semver: version=${version}, min=${minVersion}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
console.log(`[CliManager] Version ${v} meets requirements: ${min}`);
|
||||
return semver.gte(v, min);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get feature flags based on CLI version
|
||||
*
|
||||
* @param version - CLI version string
|
||||
* @returns Feature flags
|
||||
*/
|
||||
private getFeatureFlags(version: string | undefined): CliFeatureFlags {
|
||||
const isSupportedVersion = this.isVersionSupported(
|
||||
version,
|
||||
MIN_CLI_VERSION_FOR_SESSION_METHODS,
|
||||
);
|
||||
|
||||
return {
|
||||
supportsSessionList: isSupportedVersion,
|
||||
supportsSessionLoad: isSupportedVersion,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect CLI version and features
|
||||
*
|
||||
* @param forceRefresh - Force a new check, ignoring cache
|
||||
* @returns CLI version information
|
||||
*/
|
||||
async detectCliVersion(forceRefresh = false): Promise<CliVersionInfo> {
|
||||
const now = Date.now();
|
||||
|
||||
// Return cached result if available and not expired
|
||||
if (
|
||||
!forceRefresh &&
|
||||
this.cachedVersionInfo &&
|
||||
now - this.versionLastCheckTime < CliManager.CACHE_DURATION_MS
|
||||
) {
|
||||
console.log('[CliManager] Returning cached version info');
|
||||
return this.cachedVersionInfo;
|
||||
}
|
||||
|
||||
console.log('[CliManager] Detecting CLI version...');
|
||||
|
||||
try {
|
||||
// Detect CLI installation
|
||||
const detectionResult = await CliManager.detectQwenCli(forceRefresh);
|
||||
|
||||
const versionInfo: CliVersionInfo = {
|
||||
version: detectionResult.version,
|
||||
isSupported: this.isVersionSupported(
|
||||
detectionResult.version,
|
||||
MIN_CLI_VERSION_FOR_SESSION_METHODS,
|
||||
),
|
||||
features: this.getFeatureFlags(detectionResult.version),
|
||||
detectionResult,
|
||||
};
|
||||
|
||||
// Cache the result
|
||||
this.cachedVersionInfo = versionInfo;
|
||||
this.versionLastCheckTime = now;
|
||||
|
||||
console.log('[CliManager] CLI version detection result:', versionInfo);
|
||||
|
||||
return versionInfo;
|
||||
} catch (error) {
|
||||
console.error('[CliManager] Failed to detect CLI version:', error);
|
||||
|
||||
// Return fallback result
|
||||
const fallbackResult: CliVersionInfo = {
|
||||
version: undefined,
|
||||
isSupported: false,
|
||||
features: {
|
||||
supportsSessionList: false,
|
||||
supportsSessionLoad: false,
|
||||
},
|
||||
detectionResult: {
|
||||
isInstalled: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
};
|
||||
|
||||
return fallbackResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cached version information
|
||||
*/
|
||||
clearVersionCache(): void {
|
||||
this.cachedVersionInfo = null;
|
||||
this.versionLastCheckTime = 0;
|
||||
CliManager.clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if CLI supports session/list method
|
||||
*
|
||||
* @param forceRefresh - Force a new check, ignoring cache
|
||||
* @returns Whether session/list is supported
|
||||
*/
|
||||
async supportsSessionList(forceRefresh = false): Promise<boolean> {
|
||||
const versionInfo = await this.detectCliVersion(forceRefresh);
|
||||
return versionInfo.features.supportsSessionList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if CLI supports session/load method
|
||||
*
|
||||
* @param forceRefresh - Force a new check, ignoring cache
|
||||
* @returns Whether session/load is supported
|
||||
*/
|
||||
async supportsSessionLoad(forceRefresh = false): Promise<boolean> {
|
||||
const versionInfo = await this.detectCliVersion(forceRefresh);
|
||||
return versionInfo.features.supportsSessionLoad;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check CLI version with cooldown to prevent spamming notifications
|
||||
*
|
||||
* @param showNotifications - Whether to show notifications for issues
|
||||
* @returns Promise with version check result
|
||||
*/
|
||||
async checkCliVersion(showNotifications: boolean = true): Promise<{
|
||||
isInstalled: boolean;
|
||||
version?: string;
|
||||
isSupported: boolean;
|
||||
needsUpdate: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
// Detect CLI installation
|
||||
const detectionResult: CliDetectionResult =
|
||||
await CliManager.detectQwenCli();
|
||||
|
||||
if (!detectionResult.isInstalled) {
|
||||
if (showNotifications && this.canShowNotification()) {
|
||||
vscode.window.showWarningMessage(
|
||||
`Qwen Code CLI not found. Please install it using: npm install -g @qwen-code/qwen-code@latest`,
|
||||
);
|
||||
this.lastNotificationTime = Date.now();
|
||||
}
|
||||
|
||||
return {
|
||||
isInstalled: false,
|
||||
error: detectionResult.error,
|
||||
isSupported: false,
|
||||
needsUpdate: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Get version information
|
||||
const versionInfo = await this.detectCliVersion();
|
||||
|
||||
const currentVersion = detectionResult.version;
|
||||
const isSupported = versionInfo.isSupported;
|
||||
|
||||
// Check if update is needed (version is too old)
|
||||
const needsUpdate = currentVersion
|
||||
? !semver.satisfies(
|
||||
currentVersion,
|
||||
`>=${MIN_CLI_VERSION_FOR_SESSION_METHODS}`,
|
||||
)
|
||||
: false;
|
||||
|
||||
// Show notification only if needed and within cooldown period
|
||||
if (showNotifications && !isSupported && this.canShowNotification()) {
|
||||
vscode.window
|
||||
.showWarningMessage(
|
||||
`Qwen Code CLI version ${currentVersion} is below the minimum required version. Some features may not work properly. Please upgrade to version ${MIN_CLI_VERSION_FOR_SESSION_METHODS} or later`,
|
||||
'Upgrade Now',
|
||||
'View Documentation',
|
||||
)
|
||||
.then(async (selection) => {
|
||||
if (selection === 'Upgrade Now') {
|
||||
await CliInstaller.install();
|
||||
} else if (selection === 'View Documentation') {
|
||||
vscode.env.openExternal(
|
||||
vscode.Uri.parse(
|
||||
'https://github.com/QwenLM/qwen-code#installation',
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
this.lastNotificationTime = Date.now();
|
||||
}
|
||||
|
||||
return {
|
||||
isInstalled: true,
|
||||
version: currentVersion,
|
||||
isSupported,
|
||||
needsUpdate,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[CliManager] Version check failed:', error);
|
||||
|
||||
if (showNotifications && this.canShowNotification()) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to check Qwen Code CLI version: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
this.lastNotificationTime = Date.now();
|
||||
}
|
||||
|
||||
return {
|
||||
isInstalled: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
isSupported: false,
|
||||
needsUpdate: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if notification can be shown based on cooldown period
|
||||
*/
|
||||
private canShowNotification(): boolean {
|
||||
return (
|
||||
Date.now() - this.lastNotificationTime >
|
||||
CliManager.NOTIFICATION_COOLDOWN_MS
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { CliDetector, type CliDetectionResult } from './cliDetector.js';
|
||||
import {
|
||||
CliVersionManager,
|
||||
MIN_CLI_VERSION_FOR_SESSION_METHODS,
|
||||
} from './cliVersionManager.js';
|
||||
import semver from 'semver';
|
||||
|
||||
/**
|
||||
* CLI Version Checker
|
||||
*
|
||||
* Handles CLI version checking with throttling to prevent frequent notifications.
|
||||
* This class manages version checking and provides version information without
|
||||
* constantly bothering the user with popups.
|
||||
*/
|
||||
export class CliVersionChecker {
|
||||
private static instance: CliVersionChecker;
|
||||
private lastNotificationTime: number = 0;
|
||||
private static readonly NOTIFICATION_COOLDOWN_MS = 300000; // 5 minutes cooldown
|
||||
private context: vscode.ExtensionContext;
|
||||
|
||||
private constructor(context: vscode.ExtensionContext) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*/
|
||||
static getInstance(context?: vscode.ExtensionContext): CliVersionChecker {
|
||||
if (!CliVersionChecker.instance && context) {
|
||||
CliVersionChecker.instance = new CliVersionChecker(context);
|
||||
}
|
||||
return CliVersionChecker.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check CLI version with cooldown to prevent spamming notifications
|
||||
*
|
||||
* @param showNotifications - Whether to show notifications for issues
|
||||
* @returns Promise with version check result
|
||||
*/
|
||||
async checkCliVersion(showNotifications: boolean = true): Promise<{
|
||||
isInstalled: boolean;
|
||||
version?: string;
|
||||
isSupported: boolean;
|
||||
needsUpdate: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
// Detect CLI installation
|
||||
const detectionResult: CliDetectionResult =
|
||||
await CliDetector.detectQwenCli();
|
||||
|
||||
if (!detectionResult.isInstalled) {
|
||||
if (showNotifications && this.canShowNotification()) {
|
||||
vscode.window.showWarningMessage(
|
||||
`Qwen Code CLI not found. Please install it using: npm install -g @qwen-code/qwen-code@latest`,
|
||||
);
|
||||
this.lastNotificationTime = Date.now();
|
||||
}
|
||||
|
||||
return {
|
||||
isInstalled: false,
|
||||
error: detectionResult.error,
|
||||
isSupported: false,
|
||||
needsUpdate: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Get version information
|
||||
const versionManager = CliVersionManager.getInstance();
|
||||
const versionInfo = await versionManager.detectCliVersion();
|
||||
|
||||
const currentVersion = detectionResult.version;
|
||||
const isSupported = versionInfo.isSupported;
|
||||
|
||||
// Check if update is needed (version is too old)
|
||||
const needsUpdate = currentVersion
|
||||
? !semver.satisfies(
|
||||
currentVersion,
|
||||
`>=${MIN_CLI_VERSION_FOR_SESSION_METHODS}`,
|
||||
)
|
||||
: false;
|
||||
|
||||
// Show notification only if needed and within cooldown period
|
||||
if (showNotifications && !isSupported && this.canShowNotification()) {
|
||||
vscode.window.showWarningMessage(
|
||||
`Qwen Code CLI version ${currentVersion} is below the minimum required version. Some features may not work properly. Please upgrade to version ${MIN_CLI_VERSION_FOR_SESSION_METHODS} or later`,
|
||||
);
|
||||
this.lastNotificationTime = Date.now();
|
||||
}
|
||||
|
||||
return {
|
||||
isInstalled: true,
|
||||
version: currentVersion,
|
||||
isSupported,
|
||||
needsUpdate,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[CliVersionChecker] Version check failed:', error);
|
||||
|
||||
if (showNotifications && this.canShowNotification()) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to check Qwen Code CLI version: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
this.lastNotificationTime = Date.now();
|
||||
}
|
||||
|
||||
return {
|
||||
isInstalled: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
isSupported: false,
|
||||
needsUpdate: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if notification can be shown based on cooldown period
|
||||
*/
|
||||
private canShowNotification(): boolean {
|
||||
return (
|
||||
Date.now() - this.lastNotificationTime >
|
||||
CliVersionChecker.NOTIFICATION_COOLDOWN_MS
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import semver from 'semver';
|
||||
import { CliDetector, type CliDetectionResult } from './cliDetector.js';
|
||||
|
||||
export const MIN_CLI_VERSION_FOR_SESSION_METHODS = '0.5.0';
|
||||
|
||||
export interface CliFeatureFlags {
|
||||
supportsSessionList: boolean;
|
||||
supportsSessionLoad: boolean;
|
||||
}
|
||||
|
||||
export interface CliVersionInfo {
|
||||
version: string | undefined;
|
||||
isSupported: boolean;
|
||||
features: CliFeatureFlags;
|
||||
detectionResult: CliDetectionResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* CLI Version Manager
|
||||
*
|
||||
* Manages CLI version detection and feature availability based on version
|
||||
*/
|
||||
export class CliVersionManager {
|
||||
private static instance: CliVersionManager;
|
||||
private cachedVersionInfo: CliVersionInfo | null = null;
|
||||
private lastCheckTime: number = 0;
|
||||
private static readonly CACHE_DURATION_MS = 30000; // 30 seconds
|
||||
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*/
|
||||
static getInstance(): CliVersionManager {
|
||||
if (!CliVersionManager.instance) {
|
||||
CliVersionManager.instance = new CliVersionManager();
|
||||
}
|
||||
return CliVersionManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if CLI version meets minimum requirements
|
||||
*
|
||||
* @param version - Version string to check
|
||||
* @param minVersion - Minimum required version
|
||||
* @returns Whether version meets requirements
|
||||
*/
|
||||
private isVersionSupported(
|
||||
version: string | undefined,
|
||||
minVersion: string,
|
||||
): boolean {
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use semver for robust comparison (handles v-prefix, pre-release, etc.)
|
||||
const v = semver.valid(version) ?? semver.coerce(version)?.version ?? null;
|
||||
const min =
|
||||
semver.valid(minVersion) ?? semver.coerce(minVersion)?.version ?? null;
|
||||
|
||||
if (!v || !min) {
|
||||
console.warn(
|
||||
`[CliVersionManager] Invalid semver: version=${version}, min=${minVersion}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
console.log(`[CliVersionManager] Version ${v} meets requirements: ${min}`);
|
||||
return semver.gte(v, min);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get feature flags based on CLI version
|
||||
*
|
||||
* @param version - CLI version string
|
||||
* @returns Feature flags
|
||||
*/
|
||||
private getFeatureFlags(version: string | undefined): CliFeatureFlags {
|
||||
const isSupportedVersion = this.isVersionSupported(
|
||||
version,
|
||||
MIN_CLI_VERSION_FOR_SESSION_METHODS,
|
||||
);
|
||||
|
||||
return {
|
||||
supportsSessionList: isSupportedVersion,
|
||||
supportsSessionLoad: isSupportedVersion,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect CLI version and features
|
||||
*
|
||||
* @param forceRefresh - Force a new check, ignoring cache
|
||||
* @returns CLI version information
|
||||
*/
|
||||
async detectCliVersion(forceRefresh = false): Promise<CliVersionInfo> {
|
||||
const now = Date.now();
|
||||
|
||||
// Return cached result if available and not expired
|
||||
if (
|
||||
!forceRefresh &&
|
||||
this.cachedVersionInfo &&
|
||||
now - this.lastCheckTime < CliVersionManager.CACHE_DURATION_MS
|
||||
) {
|
||||
console.log('[CliVersionManager] Returning cached version info');
|
||||
return this.cachedVersionInfo;
|
||||
}
|
||||
|
||||
console.log('[CliVersionManager] Detecting CLI version...');
|
||||
|
||||
try {
|
||||
// Detect CLI installation
|
||||
const detectionResult = await CliDetector.detectQwenCli(forceRefresh);
|
||||
|
||||
const versionInfo: CliVersionInfo = {
|
||||
version: detectionResult.version,
|
||||
isSupported: this.isVersionSupported(
|
||||
detectionResult.version,
|
||||
MIN_CLI_VERSION_FOR_SESSION_METHODS,
|
||||
),
|
||||
features: this.getFeatureFlags(detectionResult.version),
|
||||
detectionResult,
|
||||
};
|
||||
|
||||
// Cache the result
|
||||
this.cachedVersionInfo = versionInfo;
|
||||
this.lastCheckTime = now;
|
||||
|
||||
console.log(
|
||||
'[CliVersionManager] CLI version detection result:',
|
||||
versionInfo,
|
||||
);
|
||||
|
||||
return versionInfo;
|
||||
} catch (error) {
|
||||
console.error('[CliVersionManager] Failed to detect CLI version:', error);
|
||||
|
||||
// Return fallback result
|
||||
const fallbackResult: CliVersionInfo = {
|
||||
version: undefined,
|
||||
isSupported: false,
|
||||
features: {
|
||||
supportsSessionList: false,
|
||||
supportsSessionLoad: false,
|
||||
},
|
||||
detectionResult: {
|
||||
isInstalled: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
};
|
||||
|
||||
return fallbackResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cached version information
|
||||
*/
|
||||
clearCache(): void {
|
||||
this.cachedVersionInfo = null;
|
||||
this.lastCheckTime = 0;
|
||||
CliDetector.clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if CLI supports session/list method
|
||||
*
|
||||
* @param forceRefresh - Force a new check, ignoring cache
|
||||
* @returns Whether session/list is supported
|
||||
*/
|
||||
async supportsSessionList(forceRefresh = false): Promise<boolean> {
|
||||
const versionInfo = await this.detectCliVersion(forceRefresh);
|
||||
return versionInfo.features.supportsSessionList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if CLI supports session/load method
|
||||
*
|
||||
* @param forceRefresh - Force a new check, ignoring cache
|
||||
* @returns Whether session/load is supported
|
||||
*/
|
||||
async supportsSessionLoad(forceRefresh = false): Promise<boolean> {
|
||||
const versionInfo = await this.detectCliVersion(forceRefresh);
|
||||
return versionInfo.features.supportsSessionLoad;
|
||||
}
|
||||
}
|
||||
@@ -43,14 +43,6 @@ vi.mock('vscode', () => ({
|
||||
registerWebviewPanelSerializer: vi.fn(() => ({
|
||||
dispose: vi.fn(),
|
||||
})),
|
||||
createStatusBarItem: vi.fn(() => ({
|
||||
text: '',
|
||||
tooltip: '',
|
||||
command: '',
|
||||
show: vi.fn(),
|
||||
hide: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
})),
|
||||
},
|
||||
workspace: {
|
||||
workspaceFolders: [],
|
||||
@@ -66,10 +58,6 @@ vi.mock('vscode', () => ({
|
||||
Uri: {
|
||||
joinPath: vi.fn(),
|
||||
},
|
||||
StatusBarAlignment: {
|
||||
Left: 1,
|
||||
Right: 2,
|
||||
},
|
||||
ExtensionMode: {
|
||||
Development: 1,
|
||||
Production: 2,
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
import { QwenSessionUpdateHandler } from './qwenSessionUpdateHandler.js';
|
||||
import { CliContextManager } from '../cli/cliContextManager.js';
|
||||
import { authMethod } from '../types/acpTypes.js';
|
||||
import { MIN_CLI_VERSION_FOR_SESSION_METHODS } from '../cli/cliVersionManager.js';
|
||||
import { MIN_CLI_VERSION_FOR_SESSION_METHODS } from '../cli/cliManager.js';
|
||||
import { isAuthenticationRequiredError } from '../utils/authErrors.js';
|
||||
import { handleAuthenticateUpdate } from '../utils/authNotificationHandler.js';
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
import type { AcpConnection } from './acpConnection.js';
|
||||
import type { QwenSessionReader } from '../services/qwenSessionReader.js';
|
||||
import { CliDetector } from '../cli/cliDetector.js';
|
||||
import { CliManager } from '../cli/cliManager.js';
|
||||
import { authMethod } from '../types/acpTypes.js';
|
||||
import { isAuthenticationRequiredError } from '../utils/authErrors.js';
|
||||
|
||||
@@ -50,7 +50,7 @@ export class QwenConnectionHandler {
|
||||
let requiresAuth = false;
|
||||
|
||||
// Check if CLI exists using standard detection (with cached results for better performance)
|
||||
const detectionResult = await CliDetector.detectQwenCli(
|
||||
const detectionResult = await CliManager.detectQwenCli(
|
||||
/* forceRefresh */ false, // Use cached results when available for better performance
|
||||
);
|
||||
if (!detectionResult.isInstalled) {
|
||||
|
||||
@@ -4,15 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Authentication Error Utility
|
||||
*
|
||||
* Used to uniformly identify and handle various authentication-related error messages.
|
||||
* Determines if re-authentication is needed by matching predefined error patterns.
|
||||
*
|
||||
* @param error - The error object or string to check
|
||||
* @returns true if it's an authentication-related error, false otherwise
|
||||
*/
|
||||
const AUTH_ERROR_PATTERNS = [
|
||||
'Authentication required', // Standard authentication request message
|
||||
'(code: -32000)', // RPC error code -32000 indicates authentication failure
|
||||
@@ -23,14 +14,6 @@ const AUTH_ERROR_PATTERNS = [
|
||||
|
||||
/**
|
||||
* Determines if the given error is authentication-related
|
||||
*
|
||||
* This function detects various forms of authentication errors, including:
|
||||
* - Direct error objects
|
||||
* - String-form error messages
|
||||
* - Other types of errors converted to strings for pattern matching
|
||||
*
|
||||
* @param error - The error object to check, can be an Error instance, string, or other type
|
||||
* @returns boolean - true if the error is authentication-related, false otherwise
|
||||
*/
|
||||
export const isAuthenticationRequiredError = (error: unknown): boolean => {
|
||||
// Null check to avoid unnecessary processing
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
import * as vscode from 'vscode';
|
||||
import type { AuthenticateUpdateNotification } from '../types/acpTypes.js';
|
||||
|
||||
// Store reference to the authentication notification to allow auto-closing
|
||||
let authNotificationDisposable: { dispose: () => void } | null = null;
|
||||
// Store reference to the current notification
|
||||
let currentNotification: Thenable<string | undefined> | null = null;
|
||||
|
||||
/**
|
||||
* Handle authentication update notifications by showing a VS Code notification
|
||||
* with the authentication URI and a copy button.
|
||||
* with the authentication URI and action buttons.
|
||||
*
|
||||
* @param data - Authentication update notification data containing the auth URI
|
||||
*/
|
||||
@@ -21,30 +21,21 @@ export function handleAuthenticateUpdate(
|
||||
): void {
|
||||
const authUri = data._meta.authUri;
|
||||
|
||||
// Dismiss any existing authentication notification
|
||||
if (authNotificationDisposable) {
|
||||
authNotificationDisposable.dispose();
|
||||
authNotificationDisposable = null;
|
||||
}
|
||||
|
||||
// Show an information message with the auth URI and copy button
|
||||
const notificationPromise = vscode.window.showInformationMessage(
|
||||
`Qwen Code needs authentication. Click the button below to open the authentication page or copy the link to your browser.`,
|
||||
// Store reference to the current notification
|
||||
currentNotification = vscode.window.showInformationMessage(
|
||||
`Qwen Code needs authentication. Click an action below:`,
|
||||
'Open in Browser',
|
||||
'Copy Link',
|
||||
'Dismiss',
|
||||
);
|
||||
|
||||
// Create a simple disposable object
|
||||
authNotificationDisposable = {
|
||||
dispose: () => {
|
||||
// We can't actually cancel the promise, but we can clear our reference
|
||||
},
|
||||
};
|
||||
|
||||
notificationPromise.then((selection) => {
|
||||
currentNotification.then((selection) => {
|
||||
if (selection === 'Open in Browser') {
|
||||
// Open the authentication URI in the default browser
|
||||
vscode.env.openExternal(vscode.Uri.parse(authUri));
|
||||
vscode.window.showInformationMessage(
|
||||
'Opening authentication page in your browser...',
|
||||
);
|
||||
} else if (selection === 'Copy Link') {
|
||||
// Copy the authentication URI to clipboard
|
||||
vscode.env.clipboard.writeText(authUri);
|
||||
@@ -54,6 +45,6 @@ export function handleAuthenticateUpdate(
|
||||
}
|
||||
|
||||
// Clear the notification reference after user interaction
|
||||
authNotificationDisposable = null;
|
||||
currentNotification = null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,12 +8,11 @@ import * as vscode from 'vscode';
|
||||
import { QwenAgentManager } from '../services/qwenAgentManager.js';
|
||||
import { ConversationStore } from '../services/conversationStore.js';
|
||||
import type { AcpPermissionRequest } from '../types/acpTypes.js';
|
||||
import { CliDetector } from '../cli/cliDetector.js';
|
||||
import { CliManager } from '../cli/cliManager.js';
|
||||
import { PanelManager } from '../webview/PanelManager.js';
|
||||
import { MessageHandler } from '../webview/MessageHandler.js';
|
||||
import { WebViewContent } from '../webview/WebViewContent.js';
|
||||
import { CliInstaller } from '../cli/cliInstaller.js';
|
||||
import { CliVersionChecker } from '../cli/cliVersionChecker.js';
|
||||
import { getFileName } from './utils/webviewUtils.js';
|
||||
import { type ApprovalModeValue } from '../types/approvalModeValueTypes.js';
|
||||
import { isAuthenticationRequiredError } from '../utils/authErrors.js';
|
||||
@@ -566,7 +565,7 @@ export class WebViewProvider {
|
||||
);
|
||||
|
||||
// Check if CLI is installed before attempting to connect
|
||||
const cliDetection = await CliDetector.detectQwenCli();
|
||||
const cliDetection = await CliManager.detectQwenCli();
|
||||
|
||||
if (!cliDetection.isInstalled) {
|
||||
console.log(
|
||||
@@ -590,7 +589,7 @@ export class WebViewProvider {
|
||||
console.log('[WebViewProvider] CLI version:', cliDetection.version);
|
||||
|
||||
// Perform version check with throttled notifications
|
||||
const versionChecker = CliVersionChecker.getInstance(this.context);
|
||||
const versionChecker = CliManager.getInstance(this.context);
|
||||
await versionChecker.checkCliVersion(true); // Silent check to avoid popup spam
|
||||
|
||||
try {
|
||||
@@ -674,7 +673,6 @@ export class WebViewProvider {
|
||||
return vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: 'Logging in to Qwen Code... ',
|
||||
cancellable: false,
|
||||
},
|
||||
async (progress) => {
|
||||
|
||||
@@ -75,7 +75,11 @@ export const AssistantMessage: React.FC<AssistantMessageProps> = ({
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
>
|
||||
<MessageContent content={content} onFileClick={onFileClick} />
|
||||
<MessageContent
|
||||
content={content}
|
||||
onFileClick={onFileClick}
|
||||
enableFileLinks={false}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user