mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
🐛 Bug Fixes Release v0.1.1 (#898)
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
QwenLogger,
|
||||
} from '../telemetry/index.js';
|
||||
import type { ContentGeneratorConfig } from '../core/contentGenerator.js';
|
||||
import { DEFAULT_DASHSCOPE_BASE_URL } from '../core/openaiContentGenerator/constants.js';
|
||||
import {
|
||||
AuthType,
|
||||
createContentGeneratorConfig,
|
||||
@@ -250,6 +251,7 @@ describe('Server Config (config.ts)', () => {
|
||||
authType,
|
||||
{
|
||||
model: MODEL,
|
||||
baseUrl: DEFAULT_DASHSCOPE_BASE_URL,
|
||||
},
|
||||
);
|
||||
// Verify that contentGeneratorConfig is updated
|
||||
|
||||
@@ -88,8 +88,9 @@ import {
|
||||
DEFAULT_FILE_FILTERING_OPTIONS,
|
||||
DEFAULT_MEMORY_FILE_FILTERING_OPTIONS,
|
||||
} from './constants.js';
|
||||
import { DEFAULT_QWEN_EMBEDDING_MODEL } from './models.js';
|
||||
import { DEFAULT_QWEN_EMBEDDING_MODEL, DEFAULT_QWEN_MODEL } from './models.js';
|
||||
import { Storage } from './storage.js';
|
||||
import { DEFAULT_DASHSCOPE_BASE_URL } from '../core/openaiContentGenerator/constants.js';
|
||||
|
||||
// Re-export types
|
||||
export type { AnyToolInvocation, FileFilteringOptions, MCPOAuthConfig };
|
||||
@@ -243,7 +244,7 @@ export interface ConfigParameters {
|
||||
fileDiscoveryService?: FileDiscoveryService;
|
||||
includeDirectories?: string[];
|
||||
bugCommand?: BugCommandSettings;
|
||||
model: string;
|
||||
model?: string;
|
||||
extensionContextFilePaths?: string[];
|
||||
maxSessionTurns?: number;
|
||||
sessionTokenLimit?: number;
|
||||
@@ -289,7 +290,7 @@ export class Config {
|
||||
private fileSystemService: FileSystemService;
|
||||
private contentGeneratorConfig!: ContentGeneratorConfig;
|
||||
private contentGenerator!: ContentGenerator;
|
||||
private readonly _generationConfig: ContentGeneratorConfig;
|
||||
private _generationConfig: Partial<ContentGeneratorConfig>;
|
||||
private readonly embeddingModel: string;
|
||||
private readonly sandbox: SandboxConfig | undefined;
|
||||
private readonly targetDir: string;
|
||||
@@ -440,8 +441,10 @@ export class Config {
|
||||
this._generationConfig = {
|
||||
model: params.model,
|
||||
...(params.generationConfig || {}),
|
||||
baseUrl: params.generationConfig?.baseUrl || DEFAULT_DASHSCOPE_BASE_URL,
|
||||
};
|
||||
this.contentGeneratorConfig = this._generationConfig;
|
||||
this.contentGeneratorConfig = this
|
||||
._generationConfig as ContentGeneratorConfig;
|
||||
this.cliVersion = params.cliVersion;
|
||||
|
||||
this.loadMemoryFromIncludeDirectories =
|
||||
@@ -520,6 +523,26 @@ export class Config {
|
||||
return this.contentGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the credentials in the generation config.
|
||||
* This is needed when credentials are set after Config construction.
|
||||
*/
|
||||
updateCredentials(credentials: {
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
model?: string;
|
||||
}): void {
|
||||
if (credentials.apiKey) {
|
||||
this._generationConfig.apiKey = credentials.apiKey;
|
||||
}
|
||||
if (credentials.baseUrl) {
|
||||
this._generationConfig.baseUrl = credentials.baseUrl;
|
||||
}
|
||||
if (credentials.model) {
|
||||
this._generationConfig.model = credentials.model;
|
||||
}
|
||||
}
|
||||
|
||||
async refreshAuth(authMethod: AuthType) {
|
||||
// Vertex and Genai have incompatible encryption and sending history with
|
||||
// throughtSignature from Genai to Vertex will fail, we need to strip them
|
||||
@@ -587,7 +610,7 @@ export class Config {
|
||||
}
|
||||
|
||||
getModel(): string {
|
||||
return this.contentGeneratorConfig.model;
|
||||
return this.contentGeneratorConfig?.model || DEFAULT_QWEN_MODEL;
|
||||
}
|
||||
|
||||
async setModel(
|
||||
|
||||
@@ -4,13 +4,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import type { ContentGenerator } from './contentGenerator.js';
|
||||
import {
|
||||
createContentGenerator,
|
||||
AuthType,
|
||||
createContentGeneratorConfig,
|
||||
} from './contentGenerator.js';
|
||||
import { createContentGenerator, AuthType } from './contentGenerator.js';
|
||||
import { createCodeAssistContentGenerator } from '../code_assist/codeAssist.js';
|
||||
import { GoogleGenAI } from '@google/genai';
|
||||
import type { Config } from '../config/config.js';
|
||||
@@ -110,83 +106,3 @@ describe('createContentGenerator', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createContentGeneratorConfig', () => {
|
||||
const mockConfig = {
|
||||
getModel: vi.fn().mockReturnValue('gemini-pro'),
|
||||
setModel: vi.fn(),
|
||||
flashFallbackHandler: vi.fn(),
|
||||
getProxy: vi.fn(),
|
||||
getEnableOpenAILogging: vi.fn().mockReturnValue(false),
|
||||
getSamplingParams: vi.fn().mockReturnValue(undefined),
|
||||
getContentGeneratorTimeout: vi.fn().mockReturnValue(undefined),
|
||||
getContentGeneratorMaxRetries: vi.fn().mockReturnValue(undefined),
|
||||
getContentGeneratorDisableCacheControl: vi.fn().mockReturnValue(undefined),
|
||||
getContentGeneratorSamplingParams: vi.fn().mockReturnValue(undefined),
|
||||
getCliVersion: vi.fn().mockReturnValue('1.0.0'),
|
||||
} as unknown as Config;
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset modules to re-evaluate imports and environment variables
|
||||
vi.resetModules();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('should configure for Gemini using GEMINI_API_KEY when set', async () => {
|
||||
vi.stubEnv('GEMINI_API_KEY', 'env-gemini-key');
|
||||
const config = await createContentGeneratorConfig(
|
||||
mockConfig,
|
||||
AuthType.USE_GEMINI,
|
||||
);
|
||||
expect(config.apiKey).toBe('env-gemini-key');
|
||||
expect(config.vertexai).toBe(false);
|
||||
});
|
||||
|
||||
it('should not configure for Gemini if GEMINI_API_KEY is empty', async () => {
|
||||
vi.stubEnv('GEMINI_API_KEY', '');
|
||||
const config = await createContentGeneratorConfig(
|
||||
mockConfig,
|
||||
AuthType.USE_GEMINI,
|
||||
);
|
||||
expect(config.apiKey).toBeUndefined();
|
||||
expect(config.vertexai).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should configure for Vertex AI using GOOGLE_API_KEY when set', async () => {
|
||||
vi.stubEnv('GOOGLE_API_KEY', 'env-google-key');
|
||||
const config = await createContentGeneratorConfig(
|
||||
mockConfig,
|
||||
AuthType.USE_VERTEX_AI,
|
||||
);
|
||||
expect(config.apiKey).toBe('env-google-key');
|
||||
expect(config.vertexai).toBe(true);
|
||||
});
|
||||
|
||||
it('should configure for Vertex AI using GCP project and location when set', async () => {
|
||||
vi.stubEnv('GOOGLE_API_KEY', undefined);
|
||||
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'env-gcp-project');
|
||||
vi.stubEnv('GOOGLE_CLOUD_LOCATION', 'env-gcp-location');
|
||||
const config = await createContentGeneratorConfig(
|
||||
mockConfig,
|
||||
AuthType.USE_VERTEX_AI,
|
||||
);
|
||||
expect(config.vertexai).toBe(true);
|
||||
expect(config.apiKey).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not configure for Vertex AI if required env vars are empty', async () => {
|
||||
vi.stubEnv('GOOGLE_API_KEY', '');
|
||||
vi.stubEnv('GOOGLE_CLOUD_PROJECT', '');
|
||||
vi.stubEnv('GOOGLE_CLOUD_LOCATION', '');
|
||||
const config = await createContentGeneratorConfig(
|
||||
mockConfig,
|
||||
AuthType.USE_VERTEX_AI,
|
||||
);
|
||||
expect(config.apiKey).toBeUndefined();
|
||||
expect(config.vertexai).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,8 +14,8 @@ import type {
|
||||
} from '@google/genai';
|
||||
import { GoogleGenAI } from '@google/genai';
|
||||
import { createCodeAssistContentGenerator } from '../code_assist/codeAssist.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { DEFAULT_QWEN_MODEL } from '../config/models.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
|
||||
import type { UserTierId } from '../code_assist/types.js';
|
||||
import { InstallationManager } from '../utils/installationManager.js';
|
||||
@@ -82,53 +82,37 @@ export function createContentGeneratorConfig(
|
||||
authType: AuthType | undefined,
|
||||
generationConfig?: Partial<ContentGeneratorConfig>,
|
||||
): ContentGeneratorConfig {
|
||||
const geminiApiKey = process.env['GEMINI_API_KEY'] || undefined;
|
||||
const googleApiKey = process.env['GOOGLE_API_KEY'] || undefined;
|
||||
const googleCloudProject = process.env['GOOGLE_CLOUD_PROJECT'] || undefined;
|
||||
const googleCloudLocation = process.env['GOOGLE_CLOUD_LOCATION'] || undefined;
|
||||
|
||||
const newContentGeneratorConfig: ContentGeneratorConfig = {
|
||||
const newContentGeneratorConfig: Partial<ContentGeneratorConfig> = {
|
||||
...(generationConfig || {}),
|
||||
model: generationConfig?.model || DEFAULT_QWEN_MODEL,
|
||||
authType,
|
||||
proxy: config?.getProxy(),
|
||||
};
|
||||
|
||||
// If we are using Google auth or we are in Cloud Shell, there is nothing else to validate for now
|
||||
if (
|
||||
authType === AuthType.LOGIN_WITH_GOOGLE ||
|
||||
authType === AuthType.CLOUD_SHELL
|
||||
) {
|
||||
return newContentGeneratorConfig;
|
||||
}
|
||||
|
||||
if (authType === AuthType.USE_GEMINI && geminiApiKey) {
|
||||
newContentGeneratorConfig.apiKey = geminiApiKey;
|
||||
newContentGeneratorConfig.vertexai = false;
|
||||
|
||||
return newContentGeneratorConfig;
|
||||
}
|
||||
|
||||
if (
|
||||
authType === AuthType.USE_VERTEX_AI &&
|
||||
(googleApiKey || (googleCloudProject && googleCloudLocation))
|
||||
) {
|
||||
newContentGeneratorConfig.apiKey = googleApiKey;
|
||||
newContentGeneratorConfig.vertexai = true;
|
||||
|
||||
return newContentGeneratorConfig;
|
||||
}
|
||||
|
||||
if (authType === AuthType.QWEN_OAUTH) {
|
||||
// For Qwen OAuth, we'll handle the API key dynamically in createContentGenerator
|
||||
// Set a special marker to indicate this is Qwen OAuth
|
||||
newContentGeneratorConfig.apiKey = 'QWEN_OAUTH_DYNAMIC_TOKEN';
|
||||
newContentGeneratorConfig.model = DEFAULT_QWEN_MODEL;
|
||||
|
||||
return newContentGeneratorConfig;
|
||||
return {
|
||||
...newContentGeneratorConfig,
|
||||
model: DEFAULT_QWEN_MODEL,
|
||||
apiKey: 'QWEN_OAUTH_DYNAMIC_TOKEN',
|
||||
} as ContentGeneratorConfig;
|
||||
}
|
||||
|
||||
return newContentGeneratorConfig;
|
||||
if (authType === AuthType.USE_OPENAI) {
|
||||
if (!newContentGeneratorConfig.apiKey) {
|
||||
throw new Error('OpenAI API key is required');
|
||||
}
|
||||
|
||||
return {
|
||||
...newContentGeneratorConfig,
|
||||
model: newContentGeneratorConfig?.model || 'qwen3-coder-plus',
|
||||
} as ContentGeneratorConfig;
|
||||
}
|
||||
|
||||
return {
|
||||
...newContentGeneratorConfig,
|
||||
model: newContentGeneratorConfig?.model || DEFAULT_QWEN_MODEL,
|
||||
} as ContentGeneratorConfig;
|
||||
}
|
||||
|
||||
export async function createContentGenerator(
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
export const DEFAULT_TIMEOUT = 120000;
|
||||
export const DEFAULT_MAX_RETRIES = 3;
|
||||
|
||||
export const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';
|
||||
export const DEFAULT_DASHSCOPE_BASE_URL =
|
||||
'https://dashscope.aliyuncs.com/compatible-mode/v1';
|
||||
export const DEFAULT_DEEPSEEK_BASE_URL = 'https://api.deepseek.com/v1';
|
||||
export const DEFAULT_OPEN_ROUTER_BASE_URL = 'https://openrouter.ai/api/v1';
|
||||
|
||||
@@ -2,7 +2,11 @@ import OpenAI from 'openai';
|
||||
import type { Config } from '../../../config/config.js';
|
||||
import type { ContentGeneratorConfig } from '../../contentGenerator.js';
|
||||
import { AuthType } from '../../contentGenerator.js';
|
||||
import { DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES } from '../constants.js';
|
||||
import {
|
||||
DEFAULT_TIMEOUT,
|
||||
DEFAULT_MAX_RETRIES,
|
||||
DEFAULT_DASHSCOPE_BASE_URL,
|
||||
} from '../constants.js';
|
||||
import { tokenLimit } from '../../tokenLimits.js';
|
||||
import type {
|
||||
OpenAICompatibleProvider,
|
||||
@@ -53,7 +57,7 @@ export class DashScopeOpenAICompatibleProvider
|
||||
buildClient(): OpenAI {
|
||||
const {
|
||||
apiKey,
|
||||
baseUrl,
|
||||
baseUrl = DEFAULT_DASHSCOPE_BASE_URL,
|
||||
timeout = DEFAULT_TIMEOUT,
|
||||
maxRetries = DEFAULT_MAX_RETRIES,
|
||||
} = this.contentGeneratorConfig;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { OpenAIContentGenerator } from '../core/openaiContentGenerator/index.js'
|
||||
import { DashScopeOpenAICompatibleProvider } from '../core/openaiContentGenerator/provider/dashscope.js';
|
||||
import type { IQwenOAuth2Client } from './qwenOAuth2.js';
|
||||
import { SharedTokenManager } from './sharedTokenManager.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { type Config } from '../config/config.js';
|
||||
import type {
|
||||
GenerateContentParameters,
|
||||
GenerateContentResponse,
|
||||
@@ -18,10 +18,7 @@ import type {
|
||||
EmbedContentResponse,
|
||||
} from '@google/genai';
|
||||
import type { ContentGeneratorConfig } from '../core/contentGenerator.js';
|
||||
|
||||
// Default fallback base URL if no endpoint is provided
|
||||
const DEFAULT_QWEN_BASE_URL =
|
||||
'https://dashscope.aliyuncs.com/compatible-mode/v1';
|
||||
import { DEFAULT_DASHSCOPE_BASE_URL } from '../core/openaiContentGenerator/constants.js';
|
||||
|
||||
/**
|
||||
* Qwen Content Generator that uses Qwen OAuth tokens with automatic refresh
|
||||
@@ -58,7 +55,7 @@ export class QwenContentGenerator extends OpenAIContentGenerator {
|
||||
* Get the current endpoint URL with proper protocol and /v1 suffix
|
||||
*/
|
||||
private getCurrentEndpoint(resourceUrl?: string): string {
|
||||
const baseEndpoint = resourceUrl || DEFAULT_QWEN_BASE_URL;
|
||||
const baseEndpoint = resourceUrl || DEFAULT_DASHSCOPE_BASE_URL;
|
||||
const suffix = '/v1';
|
||||
|
||||
// Normalize the URL: add protocol if missing, ensure /v1 suffix
|
||||
|
||||
@@ -339,6 +339,7 @@ describe('editor utils', () => {
|
||||
diffCommand.args,
|
||||
{
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
},
|
||||
);
|
||||
expect(mockSpawnOn).toHaveBeenCalledWith('close', expect.any(Function));
|
||||
|
||||
@@ -195,6 +195,7 @@ export async function openDiff(
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const childProcess = spawn(diffCommand.command, diffCommand.args, {
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
|
||||
childProcess.on('close', (code) => {
|
||||
|
||||
Reference in New Issue
Block a user