mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-31 22:19:14 +00:00
Compare commits
1 Commits
v0.1.0-pre
...
fix/max-to
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af03eaa57f |
13
.vscode/launch.json
vendored
13
.vscode/launch.json
vendored
@@ -101,13 +101,6 @@
|
||||
"env": {
|
||||
"GEMINI_SANDBOX": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Attach by Process ID",
|
||||
"processId": "${command:PickProcess}",
|
||||
"request": "attach",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"type": "node"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
@@ -122,12 +115,6 @@
|
||||
"type": "promptString",
|
||||
"description": "Enter your prompt for non-interactive mode",
|
||||
"default": "Explain this code"
|
||||
},
|
||||
{
|
||||
"id": "debugPort",
|
||||
"type": "promptString",
|
||||
"description": "Enter the debug port number (default: 9229)",
|
||||
"default": "9229"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.1.0-preview",
|
||||
"version": "0.0.12",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.1.0-preview",
|
||||
"version": "0.0.12",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
@@ -13454,7 +13454,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.1.0-preview",
|
||||
"version": "0.0.12",
|
||||
"dependencies": {
|
||||
"@google/genai": "1.9.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
@@ -13662,7 +13662,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@qwen-code/qwen-code-core",
|
||||
"version": "0.1.0-preview",
|
||||
"version": "0.0.12",
|
||||
"dependencies": {
|
||||
"@google/genai": "1.13.0",
|
||||
"@lvce-editor/ripgrep": "^1.6.0",
|
||||
@@ -13788,7 +13788,7 @@
|
||||
},
|
||||
"packages/test-utils": {
|
||||
"name": "@qwen-code/qwen-code-test-utils",
|
||||
"version": "0.1.0-preview",
|
||||
"version": "0.0.12",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
@@ -13800,7 +13800,7 @@
|
||||
},
|
||||
"packages/vscode-ide-companion": {
|
||||
"name": "qwen-code-vscode-ide-companion",
|
||||
"version": "0.1.0-preview",
|
||||
"version": "0.0.12",
|
||||
"license": "LICENSE",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.15.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.1.0-preview",
|
||||
"version": "0.0.12",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
@@ -13,7 +13,7 @@
|
||||
"url": "git+https://github.com/QwenLM/qwen-code.git"
|
||||
},
|
||||
"config": {
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.1.0-preview"
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.12"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node scripts/start.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.1.0-preview",
|
||||
"version": "0.0.12",
|
||||
"description": "Qwen Code",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -25,7 +25,7 @@
|
||||
"dist"
|
||||
],
|
||||
"config": {
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.1.0-preview"
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "1.9.0",
|
||||
|
||||
@@ -126,18 +126,6 @@ describe('validateNonInterActiveAuth', () => {
|
||||
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_OPENAI);
|
||||
});
|
||||
|
||||
it('uses configured QWEN_OAUTH if provided', async () => {
|
||||
const nonInteractiveConfig: NonInteractiveConfig = {
|
||||
refreshAuth: refreshAuthMock,
|
||||
};
|
||||
await validateNonInteractiveAuth(
|
||||
AuthType.QWEN_OAUTH,
|
||||
undefined,
|
||||
nonInteractiveConfig,
|
||||
);
|
||||
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.QWEN_OAUTH);
|
||||
});
|
||||
|
||||
it('uses USE_VERTEX_AI if GOOGLE_GENAI_USE_VERTEXAI is true (with GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION)', async () => {
|
||||
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
||||
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
||||
|
||||
@@ -97,18 +97,6 @@ class GeminiAgent {
|
||||
name: 'Vertex AI',
|
||||
description: null,
|
||||
},
|
||||
{
|
||||
id: AuthType.USE_OPENAI,
|
||||
name: 'Use OpenAI API key',
|
||||
description:
|
||||
'Requires setting the `OPENAI_API_KEY` environment variable',
|
||||
},
|
||||
{
|
||||
id: AuthType.QWEN_OAUTH,
|
||||
name: 'Qwen OAuth',
|
||||
description:
|
||||
'OAuth authentication for Qwen models with 2000 daily requests',
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code-core",
|
||||
"version": "0.1.0-preview",
|
||||
"version": "0.0.12",
|
||||
"description": "Qwen Code Core",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -712,6 +712,8 @@ async function authWithQwenDeviceFlow(
|
||||
`Polling... (attempt ${attempt + 1}/${maxAttempts})`,
|
||||
);
|
||||
|
||||
process.stdout.write('.');
|
||||
|
||||
// Wait with cancellation check every 100ms
|
||||
await new Promise<void>((resolve) => {
|
||||
const checkInterval = 100; // Check every 100ms
|
||||
|
||||
@@ -901,37 +901,5 @@ describe('SharedTokenManager', () => {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should properly clean up timeout when file operation completes before timeout', async () => {
|
||||
const tokenManager = SharedTokenManager.getInstance();
|
||||
tokenManager.clearCache();
|
||||
|
||||
const mockClient = {
|
||||
getCredentials: vi.fn().mockReturnValue(null),
|
||||
setCredentials: vi.fn(),
|
||||
getAccessToken: vi.fn(),
|
||||
requestDeviceAuthorization: vi.fn(),
|
||||
pollDeviceToken: vi.fn(),
|
||||
refreshAccessToken: vi.fn(),
|
||||
};
|
||||
|
||||
// Mock clearTimeout to verify it's called
|
||||
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
||||
|
||||
// Mock file stat to resolve quickly (before timeout)
|
||||
mockFs.stat.mockResolvedValue({ mtimeMs: 12345 } as Stats);
|
||||
|
||||
// Call checkAndReloadIfNeeded which uses withTimeout internally
|
||||
const checkMethod = getPrivateProperty(
|
||||
tokenManager,
|
||||
'checkAndReloadIfNeeded',
|
||||
) as (client?: IQwenOAuth2Client) => Promise<void>;
|
||||
await checkMethod.call(tokenManager, mockClient);
|
||||
|
||||
// Verify that clearTimeout was called to clean up the timer
|
||||
expect(clearTimeoutSpy).toHaveBeenCalled();
|
||||
|
||||
clearTimeoutSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -290,36 +290,6 @@ export class SharedTokenManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to add timeout to any promise operation
|
||||
* Properly cleans up the timeout when the promise completes
|
||||
*/
|
||||
private withTimeout<T>(
|
||||
promise: Promise<T>,
|
||||
timeoutMs: number,
|
||||
operationType = 'Operation',
|
||||
): Promise<T> {
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
|
||||
return Promise.race([
|
||||
promise.finally(() => {
|
||||
// Clear timeout when main promise completes (success or failure)
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}),
|
||||
new Promise<never>((_, reject) => {
|
||||
timeoutId = setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
new Error(`${operationType} timed out after ${timeoutMs}ms`),
|
||||
),
|
||||
timeoutMs,
|
||||
);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual file check and reload operation
|
||||
* This is separated to enable proper promise-based synchronization
|
||||
@@ -333,12 +303,25 @@ export class SharedTokenManager {
|
||||
|
||||
try {
|
||||
const filePath = this.getCredentialFilePath();
|
||||
// Add timeout to file stat operation
|
||||
const withTimeout = async <T>(
|
||||
promise: Promise<T>,
|
||||
timeoutMs: number,
|
||||
): Promise<T> =>
|
||||
Promise.race([
|
||||
promise,
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
new Error(`File operation timed out after ${timeoutMs}ms`),
|
||||
),
|
||||
timeoutMs,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
const stats = await this.withTimeout(
|
||||
fs.stat(filePath),
|
||||
3000,
|
||||
'File operation',
|
||||
);
|
||||
const stats = await withTimeout(fs.stat(filePath), 3000);
|
||||
const fileModTime = stats.mtimeMs;
|
||||
|
||||
// Reload credentials if file has been modified since last cache
|
||||
@@ -468,7 +451,7 @@ export class SharedTokenManager {
|
||||
// Check if we have a refresh token before attempting refresh
|
||||
const currentCredentials = qwenClient.getCredentials();
|
||||
if (!currentCredentials.refresh_token) {
|
||||
// console.debug('create a NO_REFRESH_TOKEN error');
|
||||
console.debug('create a NO_REFRESH_TOKEN error');
|
||||
throw new TokenManagerError(
|
||||
TokenError.NO_REFRESH_TOKEN,
|
||||
'No refresh token available for token refresh',
|
||||
@@ -606,12 +589,26 @@ export class SharedTokenManager {
|
||||
const dirPath = path.dirname(filePath);
|
||||
const tempPath = `${filePath}.tmp.${randomUUID()}`;
|
||||
|
||||
// Add timeout wrapper for file operations
|
||||
const withTimeout = async <T>(
|
||||
promise: Promise<T>,
|
||||
timeoutMs: number,
|
||||
): Promise<T> =>
|
||||
Promise.race([
|
||||
promise,
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)),
|
||||
timeoutMs,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
// Create directory with restricted permissions
|
||||
try {
|
||||
await this.withTimeout(
|
||||
await withTimeout(
|
||||
fs.mkdir(dirPath, { recursive: true, mode: 0o700 }),
|
||||
5000,
|
||||
'File operation',
|
||||
);
|
||||
} catch (error) {
|
||||
throw new TokenManagerError(
|
||||
@@ -625,30 +622,21 @@ export class SharedTokenManager {
|
||||
|
||||
try {
|
||||
// Write to temporary file first with restricted permissions
|
||||
await this.withTimeout(
|
||||
await withTimeout(
|
||||
fs.writeFile(tempPath, credString, { mode: 0o600 }),
|
||||
5000,
|
||||
'File operation',
|
||||
);
|
||||
|
||||
// Atomic move to final location
|
||||
await this.withTimeout(
|
||||
fs.rename(tempPath, filePath),
|
||||
5000,
|
||||
'File operation',
|
||||
);
|
||||
await withTimeout(fs.rename(tempPath, filePath), 5000);
|
||||
|
||||
// Update cached file modification time atomically after successful write
|
||||
const stats = await this.withTimeout(
|
||||
fs.stat(filePath),
|
||||
5000,
|
||||
'File operation',
|
||||
);
|
||||
const stats = await withTimeout(fs.stat(filePath), 5000);
|
||||
this.memoryCache.fileModTime = stats.mtimeMs;
|
||||
} catch (error) {
|
||||
// Clean up temp file if it exists
|
||||
try {
|
||||
await this.withTimeout(fs.unlink(tempPath), 1000, 'File operation');
|
||||
await withTimeout(fs.unlink(tempPath), 1000);
|
||||
} catch (_cleanupError) {
|
||||
// Ignore cleanup errors - temp file might not exist
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { EOL } from 'node:os';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { rgPath } from '@lvce-editor/ripgrep';
|
||||
import type { ToolInvocation, ToolResult } from './tools.js';
|
||||
import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js';
|
||||
import { SchemaValidator } from '../utils/schemaValidator.js';
|
||||
@@ -17,14 +18,6 @@ import type { Config } from '../config/config.js';
|
||||
|
||||
const DEFAULT_TOTAL_MAX_MATCHES = 20000;
|
||||
|
||||
/**
|
||||
* Lazy loads the ripgrep binary path to avoid loading the library until needed
|
||||
*/
|
||||
async function getRipgrepPath(): Promise<string> {
|
||||
const { rgPath } = await import('@lvce-editor/ripgrep');
|
||||
return rgPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for the GrepTool
|
||||
*/
|
||||
@@ -299,9 +292,8 @@ class GrepToolInvocation extends BaseToolInvocation<
|
||||
rgArgs.push(absolutePath);
|
||||
|
||||
try {
|
||||
const ripgrepPath = await getRipgrepPath();
|
||||
const output = await new Promise<string>((resolve, reject) => {
|
||||
const child = spawn(ripgrepPath, rgArgs, {
|
||||
const child = spawn(rgPath, rgArgs, {
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code-test-utils",
|
||||
"version": "0.1.0-preview",
|
||||
"version": "0.0.12",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "qwen-code-vscode-ide-companion",
|
||||
"displayName": "Qwen Code Companion",
|
||||
"description": "Enable Qwen Code with direct access to your VS Code workspace.",
|
||||
"version": "0.1.0-preview",
|
||||
"version": "0.0.12",
|
||||
"publisher": "qwenlm",
|
||||
"icon": "assets/icon.png",
|
||||
"repository": {
|
||||
|
||||
Reference in New Issue
Block a user