fix: qwen vscode extension

This commit is contained in:
tanzhenxin
2025-08-19 18:20:40 +08:00
parent a19db16485
commit 303b6999f4
14 changed files with 72 additions and 70 deletions

View File

@@ -1,23 +1,23 @@
# Gemini CLI Extensions # Qwen Code Extensions
Gemini CLI supports extensions that can be used to configure and extend its functionality. Qwen Code supports extensions that can be used to configure and extend its functionality.
## How it works ## How it works
On startup, Gemini CLI looks for extensions in two locations: On startup, Qwen Code looks for extensions in two locations:
1. `<workspace>/.gemini/extensions` 1. `<workspace>/.qwen/extensions`
2. `<home>/.gemini/extensions` 2. `<home>/.qwen/extensions`
Gemini CLI loads all extensions from both locations. If an extension with the same name exists in both locations, the extension in the workspace directory takes precedence. Qwen Code loads all extensions from both locations. If an extension with the same name exists in both locations, the extension in the workspace directory takes precedence.
Within each location, individual extensions exist as a directory that contains a `gemini-extension.json` file. For example: Within each location, individual extensions exist as a directory that contains a `qwen-extension.json` file. For example:
`<workspace>/.gemini/extensions/my-extension/gemini-extension.json` `<workspace>/.qwen/extensions/my-extension/qwen-extension.json`
### `gemini-extension.json` ### `qwen-extension.json`
The `gemini-extension.json` file contains the configuration for the extension. The file has the following structure: The `qwen-extension.json` file contains the configuration for the extension. The file has the following structure:
```json ```json
{ {
@@ -39,7 +39,7 @@ The `gemini-extension.json` file contains the configuration for the extension. T
- `contextFileName`: The name of the file that contains the context for the extension. This will be used to load the context from the workspace. If this property is not used but a `QWEN.md` file is present in your extension directory, then that file will be loaded. - `contextFileName`: The name of the file that contains the context for the extension. This will be used to load the context from the workspace. If this property is not used but a `QWEN.md` file is present in your extension directory, then that file will be loaded.
- `excludeTools`: An array of tool names to exclude from the model. You can also specify command-specific restrictions for tools that support it, like the `run_shell_command` tool. For example, `"excludeTools": ["run_shell_command(rm -rf)"]` will block the `rm -rf` command. - `excludeTools`: An array of tool names to exclude from the model. You can also specify command-specific restrictions for tools that support it, like the `run_shell_command` tool. For example, `"excludeTools": ["run_shell_command(rm -rf)"]` will block the `rm -rf` command.
When Gemini CLI starts, it loads all the extensions and merges their configurations. If there are any conflicts, the workspace configuration takes precedence. When Qwen Code starts, it loads all the extensions and merges their configurations. If there are any conflicts, the workspace configuration takes precedence.
## Extension Commands ## Extension Commands
@@ -50,8 +50,8 @@ Extensions can provide [custom commands](./cli/commands.md#custom-commands) by p
An extension named `gcp` with the following structure: An extension named `gcp` with the following structure:
``` ```
.gemini/extensions/gcp/ .qwen/extensions/gcp/
├── gemini-extension.json ├── qwen-extension.json
└── commands/ └── commands/
├── deploy.toml ├── deploy.toml
└── gcs/ └── gcs/

View File

@@ -1093,7 +1093,7 @@ describe('loadCliConfig ideModeFeature', () => {
vi.mocked(os.homedir).mockReturnValue('/mock/home/user'); vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
process.env.GEMINI_API_KEY = 'test-api-key'; process.env.GEMINI_API_KEY = 'test-api-key';
delete process.env.SANDBOX; delete process.env.SANDBOX;
delete process.env.GEMINI_CLI_IDE_SERVER_PORT; delete process.env.QWEN_CODE_IDE_SERVER_PORT;
}); });
afterEach(() => { afterEach(() => {

View File

@@ -10,7 +10,8 @@ import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
export const EXTENSIONS_DIRECTORY_NAME = path.join('.qwen', 'extensions'); export const EXTENSIONS_DIRECTORY_NAME = path.join('.qwen', 'extensions');
export const EXTENSIONS_CONFIG_FILENAME = 'gemini-extension.json'; export const EXTENSIONS_CONFIG_FILENAME = 'qwen-extension.json';
export const EXTENSIONS_CONFIG_FILENAME_OLD = 'gemini-extension.json';
export interface Extension { export interface Extension {
path: string; path: string;
@@ -68,12 +69,19 @@ function loadExtension(extensionDir: string): Extension | null {
return null; return null;
} }
const configFilePath = path.join(extensionDir, EXTENSIONS_CONFIG_FILENAME); let configFilePath = path.join(extensionDir, EXTENSIONS_CONFIG_FILENAME);
if (!fs.existsSync(configFilePath)) { if (!fs.existsSync(configFilePath)) {
console.error( const oldConfigFilePath = path.join(
`Warning: extension directory ${extensionDir} does not contain a config file ${configFilePath}.`, extensionDir,
EXTENSIONS_CONFIG_FILENAME_OLD,
); );
return null; if (!fs.existsSync(oldConfigFilePath)) {
console.error(
`Warning: extension directory ${extensionDir} does not contain a config file ${configFilePath}.`,
);
return null;
}
configFilePath = oldConfigFilePath;
} }
try { try {

View File

@@ -631,8 +631,8 @@ export async function start_sandbox(
// Pass through IDE mode environment variables // Pass through IDE mode environment variables
for (const envVar of [ for (const envVar of [
'GEMINI_CLI_IDE_SERVER_PORT', 'QWEN_CODE_IDE_SERVER_PORT',
'GEMINI_CLI_IDE_WORKSPACE_PATH', 'QWEN_CODE_IDE_WORKSPACE_PATH',
'TERM_PROGRAM', 'TERM_PROGRAM',
]) { ]) {
if (process.env[envVar]) { if (process.env[envVar]) {

View File

@@ -83,7 +83,7 @@ export class IdeClient {
if (!this.currentIde || !this.currentIdeDisplayName) { if (!this.currentIde || !this.currentIdeDisplayName) {
this.setState( this.setState(
IDEConnectionStatus.Disconnected, IDEConnectionStatus.Disconnected,
`IDE integration is not supported in your current environment. To use this feature, run Gemini CLI in one of these supported IDEs: ${Object.values( `IDE integration is not supported in your current environment. To use this feature, run Qwen Code in one of these supported IDEs: ${Object.values(
DetectedIde, DetectedIde,
) )
.map((ide) => getIdeDisplayName(ide)) .map((ide) => getIdeDisplayName(ide))
@@ -233,7 +233,7 @@ export class IdeClient {
} }
private validateWorkspacePath(): boolean { private validateWorkspacePath(): boolean {
const ideWorkspacePath = process.env['GEMINI_CLI_IDE_WORKSPACE_PATH']; const ideWorkspacePath = process.env['QWEN_CODE_IDE_WORKSPACE_PATH'];
if (ideWorkspacePath === undefined) { if (ideWorkspacePath === undefined) {
this.setState( this.setState(
IDEConnectionStatus.Disconnected, IDEConnectionStatus.Disconnected,
@@ -257,7 +257,7 @@ export class IdeClient {
if (rel.startsWith('..') || path.isAbsolute(rel)) { if (rel.startsWith('..') || path.isAbsolute(rel)) {
this.setState( this.setState(
IDEConnectionStatus.Disconnected, IDEConnectionStatus.Disconnected,
`Directory mismatch. Gemini CLI is running in a different location than the open workspace in ${this.currentIdeDisplayName}. Please run the CLI from the same directory as your project's root folder.`, `Directory mismatch. Qwen Code is running in a different location than the open workspace in ${this.currentIdeDisplayName}. Please run the CLI from the same directory as your project's root folder.`,
true, true,
); );
return false; return false;
@@ -266,7 +266,7 @@ export class IdeClient {
} }
private getPortFromEnv(): string | undefined { private getPortFromEnv(): string | undefined {
const port = process.env['GEMINI_CLI_IDE_SERVER_PORT']; const port = process.env['QWEN_CODE_IDE_SERVER_PORT'];
if (!port) { if (!port) {
this.setState( this.setState(
IDEConnectionStatus.Disconnected, IDEConnectionStatus.Disconnected,

View File

@@ -86,7 +86,7 @@ describe('ide-installer', () => {
.mockImplementation(() => ''); .mockImplementation(() => '');
const result = await installer.install(); const result = await installer.install();
expect(execSyncSpy).toHaveBeenCalledWith( expect(execSyncSpy).toHaveBeenCalledWith(
'npx ovsx get google.gemini-cli-vscode-ide-companion', 'npx ovsx get qwenlm.qwen-code-vscode-ide-companion',
{ stdio: 'pipe' }, { stdio: 'pipe' },
); );
expect(result.success).toBe(true); expect(result.success).toBe(true);

View File

@@ -150,7 +150,7 @@ class VsCodeInstaller implements IdeInstaller {
class OpenVSXInstaller implements IdeInstaller { class OpenVSXInstaller implements IdeInstaller {
async install(): Promise<InstallResult> { async install(): Promise<InstallResult> {
// TODO: Use the correct extension path. // TODO: Use the correct extension path.
const command = `npx ovsx get google.gemini-cli-vscode-ide-companion`; const command = `npx ovsx get qwenlm.qwen-code-vscode-ide-companion`;
try { try {
child_process.execSync(command, { stdio: 'pipe' }); child_process.execSync(command, { stdio: 'pipe' });
return { return {

View File

@@ -8,9 +8,9 @@ The Qwen Code Companion extension seamlessly integrates [Qwen Code](https://gith
- Selection Context: Qwen Code can easily access your cursor's position and selected text within the editor, giving it valuable context directly from your current work. - Selection Context: Qwen Code can easily access your cursor's position and selected text within the editor, giving it valuable context directly from your current work.
- Native Diffing: Seamlessly view, modify, and accept code changes suggested by Gemini CLI directly within the editor. - Native Diffing: Seamlessly view, modify, and accept code changes suggested by Qwen Code directly within the editor.
- Launch Gemini CLI: Quickly start a new Gemini CLI session from the Command Palette (Cmd+Shift+P or Ctrl+Shift+P) by running the "Gemini CLI: Run" command. - Launch Qwen Code: Quickly start a new Qwen Code session from the Command Palette (Cmd+Shift+P or Ctrl+Shift+P) by running the "Qwen Code: Run" command.
# Requirements # Requirements

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -48,7 +48,7 @@
"icon": "$(close)" "icon": "$(close)"
}, },
{ {
"command": "qwen-code.runGeminiCLI", "command": "qwen-code.runQwenCode",
"title": "Qwen Code: Run" "title": "Qwen Code: Run"
}, },
{ {

View File

@@ -97,7 +97,7 @@ export class DiffManager {
const diffTitle = `${path.basename(filePath)} ↔ Modified`; const diffTitle = `${path.basename(filePath)} ↔ Modified`;
await vscode.commands.executeCommand( await vscode.commands.executeCommand(
'setContext', 'setContext',
'gemini.diff.isVisible', 'qwen.diff.isVisible',
true, true,
); );
@@ -228,7 +228,7 @@ export class DiffManager {
} }
await vscode.commands.executeCommand( await vscode.commands.executeCommand(
'setContext', 'setContext',
'gemini.diff.isVisible', 'qwen.diff.isVisible',
isVisible, isVisible,
); );
} }
@@ -241,7 +241,7 @@ export class DiffManager {
const diffInfo = this.diffDocuments.get(rightDocUri.toString()); const diffInfo = this.diffDocuments.get(rightDocUri.toString());
await vscode.commands.executeCommand( await vscode.commands.executeCommand(
'setContext', 'setContext',
'gemini.diff.isVisible', 'qwen.diff.isVisible',
false, false,
); );

View File

@@ -80,8 +80,8 @@ describe('activate', () => {
vi.mocked(context.globalState.get).mockReturnValue(undefined); vi.mocked(context.globalState.get).mockReturnValue(undefined);
await activate(context); await activate(context);
expect(showInformationMessageMock).toHaveBeenCalledWith( expect(showInformationMessageMock).toHaveBeenCalledWith(
'Gemini CLI Companion extension successfully installed. Please restart your terminal to enable full IDE integration.', 'Qwen Code Companion extension successfully installed. Please restart your terminal to enable full IDE integration.',
'Re-launch Gemini CLI', 'Run Qwen Code',
); );
}); });
@@ -91,16 +91,16 @@ describe('activate', () => {
expect(vscode.window.showInformationMessage).not.toHaveBeenCalled(); expect(vscode.window.showInformationMessage).not.toHaveBeenCalled();
}); });
it('should launch the Gemini CLI when the user clicks the button', async () => { it('should launch Qwen Code when the user clicks the button', async () => {
const showInformationMessageMock = vi const showInformationMessageMock = vi
.mocked(vscode.window.showInformationMessage) .mocked(vscode.window.showInformationMessage)
.mockResolvedValue('Re-launch Gemini CLI' as never); .mockResolvedValue('Run Qwen Code' as never);
vi.mocked(context.globalState.get).mockReturnValue(undefined); vi.mocked(context.globalState.get).mockReturnValue(undefined);
await activate(context); await activate(context);
expect(showInformationMessageMock).toHaveBeenCalled(); expect(showInformationMessageMock).toHaveBeenCalled();
await new Promise(process.nextTick); // Wait for the promise to resolve await new Promise(process.nextTick); // Wait for the promise to resolve
expect(vscode.commands.executeCommand).toHaveBeenCalledWith( expect(vscode.commands.executeCommand).toHaveBeenCalledWith(
'gemini-cli.runGeminiCLI', 'qwen-code.runQwenCode',
); );
}); });
}); });

View File

@@ -9,9 +9,9 @@ import { IDEServer } from './ide-server.js';
import { DiffContentProvider, DiffManager } from './diff-manager.js'; import { DiffContentProvider, DiffManager } from './diff-manager.js';
import { createLogger } from './utils/logger.js'; import { createLogger } from './utils/logger.js';
const INFO_MESSAGE_SHOWN_KEY = 'geminiCliInfoMessageShown'; const INFO_MESSAGE_SHOWN_KEY = 'qwenCodeInfoMessageShown';
const IDE_WORKSPACE_PATH_ENV_VAR = 'GEMINI_CLI_IDE_WORKSPACE_PATH'; const IDE_WORKSPACE_PATH_ENV_VAR = 'QWEN_CODE_IDE_WORKSPACE_PATH';
export const DIFF_SCHEME = 'gemini-diff'; export const DIFF_SCHEME = 'qwen-diff';
let ideServer: IDEServer; let ideServer: IDEServer;
let logger: vscode.OutputChannel; let logger: vscode.OutputChannel;
@@ -35,7 +35,7 @@ function updateWorkspacePath(context: vscode.ExtensionContext) {
} }
export async function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext) {
logger = vscode.window.createOutputChannel('Gemini CLI IDE Companion'); logger = vscode.window.createOutputChannel('Qwen Code Companion');
log = createLogger(context, logger); log = createLogger(context, logger);
log('Extension activated'); log('Extension activated');
@@ -54,24 +54,18 @@ export async function activate(context: vscode.ExtensionContext) {
DIFF_SCHEME, DIFF_SCHEME,
diffContentProvider, diffContentProvider,
), ),
vscode.commands.registerCommand( vscode.commands.registerCommand('qwen.diff.accept', (uri?: vscode.Uri) => {
'gemini.diff.accept', const docUri = uri ?? vscode.window.activeTextEditor?.document.uri;
(uri?: vscode.Uri) => { if (docUri && docUri.scheme === DIFF_SCHEME) {
const docUri = uri ?? vscode.window.activeTextEditor?.document.uri; diffManager.acceptDiff(docUri);
if (docUri && docUri.scheme === DIFF_SCHEME) { }
diffManager.acceptDiff(docUri); }),
} vscode.commands.registerCommand('qwen.diff.cancel', (uri?: vscode.Uri) => {
}, const docUri = uri ?? vscode.window.activeTextEditor?.document.uri;
), if (docUri && docUri.scheme === DIFF_SCHEME) {
vscode.commands.registerCommand( diffManager.cancelDiff(docUri);
'gemini.diff.cancel', }
(uri?: vscode.Uri) => { }),
const docUri = uri ?? vscode.window.activeTextEditor?.document.uri;
if (docUri && docUri.scheme === DIFF_SCHEME) {
diffManager.cancelDiff(docUri);
}
},
),
); );
ideServer = new IDEServer(log, diffManager); ideServer = new IDEServer(log, diffManager);
@@ -85,13 +79,13 @@ export async function activate(context: vscode.ExtensionContext) {
if (!context.globalState.get(INFO_MESSAGE_SHOWN_KEY)) { if (!context.globalState.get(INFO_MESSAGE_SHOWN_KEY)) {
void vscode.window void vscode.window
.showInformationMessage( .showInformationMessage(
'Gemini CLI Companion extension successfully installed. Please restart your terminal to enable full IDE integration.', 'Qwen Code Companion extension successfully installed. Please restart your terminal to enable full IDE integration.',
'Re-launch Gemini CLI', 'Run Qwen Code',
) )
.then( .then(
(selection) => { (selection) => {
if (selection === 'Re-launch Gemini CLI') { if (selection === 'Run Qwen Code') {
void vscode.commands.executeCommand('gemini-cli.runGeminiCLI'); void vscode.commands.executeCommand('qwen-code.runQwenCode');
} }
}, },
(err) => { (err) => {
@@ -105,13 +99,13 @@ export async function activate(context: vscode.ExtensionContext) {
vscode.workspace.onDidChangeWorkspaceFolders(() => { vscode.workspace.onDidChangeWorkspaceFolders(() => {
updateWorkspacePath(context); updateWorkspacePath(context);
}), }),
vscode.commands.registerCommand('gemini-cli.runGeminiCLI', () => { vscode.commands.registerCommand('qwen-code.runQwenCode', () => {
const geminiCmd = 'gemini'; const qwenCmd = 'qwen';
const terminal = vscode.window.createTerminal(`Gemini CLI`); const terminal = vscode.window.createTerminal(`Qwen Code`);
terminal.show(); terminal.show();
terminal.sendText(geminiCmd); terminal.sendText(qwenCmd);
}), }),
vscode.commands.registerCommand('gemini-cli.showNotices', async () => { vscode.commands.registerCommand('qwen-code.showNotices', async () => {
const noticePath = vscode.Uri.joinPath( const noticePath = vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
'NOTICES.txt', 'NOTICES.txt',

View File

@@ -17,7 +17,7 @@ import { DiffManager } from './diff-manager.js';
import { OpenFilesManager } from './open-files-manager.js'; import { OpenFilesManager } from './open-files-manager.js';
const MCP_SESSION_ID_HEADER = 'mcp-session-id'; const MCP_SESSION_ID_HEADER = 'mcp-session-id';
const IDE_SERVER_PORT_ENV_VAR = 'GEMINI_CLI_IDE_SERVER_PORT'; const IDE_SERVER_PORT_ENV_VAR = 'QWEN_CODE_IDE_SERVER_PORT';
function sendIdeContextUpdateNotification( function sendIdeContextUpdateNotification(
transport: StreamableHTTPServerTransport, transport: StreamableHTTPServerTransport,