mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 16:57:46 +00:00
merge main branch functionalities
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import * as os from 'os';
|
||||
import { loadCliConfig, parseArguments } from './config.js';
|
||||
import { loadCliConfig, parseArguments, CliArgs } from './config.js';
|
||||
import { Settings } from './settings.js';
|
||||
import { Extension } from './extension.js';
|
||||
import * as ServerConfig from '@qwen-code/qwen-code-core';
|
||||
@@ -561,6 +561,68 @@ describe('mergeMcpServers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadCliConfig systemPromptMappings', () => {
|
||||
it('should use default systemPromptMappings when not provided in settings', async () => {
|
||||
const mockSettings: Settings = {
|
||||
theme: 'dark',
|
||||
};
|
||||
const mockExtensions: Extension[] = [];
|
||||
const mockSessionId = 'test-session';
|
||||
const mockArgv: CliArgs = {
|
||||
model: 'test-model',
|
||||
} as CliArgs;
|
||||
|
||||
const config = await loadCliConfig(
|
||||
mockSettings,
|
||||
mockExtensions,
|
||||
mockSessionId,
|
||||
mockArgv,
|
||||
);
|
||||
|
||||
expect(config.getSystemPromptMappings()).toEqual([
|
||||
{
|
||||
baseUrls: [
|
||||
'https://dashscope.aliyuncs.com/compatible-mode/v1/',
|
||||
'https://dashscope-intl.aliyuncs.com/compatible-mode/v1/',
|
||||
],
|
||||
modelNames: ['qwen3-coder-plus'],
|
||||
template:
|
||||
'SYSTEM_TEMPLATE:{"name":"qwen3_coder","params":{"is_git_repository":{RUNTIME_VARS_IS_GIT_REPO},"sandbox":"{RUNTIME_VARS_SANDBOX}"}}',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should use custom systemPromptMappings when provided in settings', async () => {
|
||||
const customSystemPromptMappings = [
|
||||
{
|
||||
baseUrls: ['https://custom-api.com'],
|
||||
modelNames: ['custom-model'],
|
||||
template: 'Custom template',
|
||||
},
|
||||
];
|
||||
const mockSettings: Settings = {
|
||||
theme: 'dark',
|
||||
systemPromptMappings: customSystemPromptMappings,
|
||||
};
|
||||
const mockExtensions: Extension[] = [];
|
||||
const mockSessionId = 'test-session';
|
||||
const mockArgv: CliArgs = {
|
||||
model: 'test-model',
|
||||
} as CliArgs;
|
||||
|
||||
const config = await loadCliConfig(
|
||||
mockSettings,
|
||||
mockExtensions,
|
||||
mockSessionId,
|
||||
mockArgv,
|
||||
);
|
||||
|
||||
expect(config.getSystemPromptMappings()).toEqual(
|
||||
customSystemPromptMappings,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeExcludeTools', () => {
|
||||
it('should merge excludeTools from settings and extensions', async () => {
|
||||
const settings: Settings = { excludeTools: ['tool1', 'tool2'] };
|
||||
|
||||
@@ -441,6 +441,8 @@ export async function loadCliConfig(
|
||||
model: argv.model!,
|
||||
extensionContextFilePaths,
|
||||
maxSessionTurns: settings.maxSessionTurns ?? -1,
|
||||
sessionTokenLimit: settings.sessionTokenLimit ?? 32000,
|
||||
maxFolderItems: settings.maxFolderItems ?? 20,
|
||||
experimentalAcp: argv.experimentalAcp || false,
|
||||
listExtensions: argv.listExtensions || false,
|
||||
extensions: allExtensions,
|
||||
@@ -465,6 +467,7 @@ export async function loadCliConfig(
|
||||
'SYSTEM_TEMPLATE:{"name":"qwen3_coder","params":{"is_git_repository":{RUNTIME_VARS_IS_GIT_REPO},"sandbox":"{RUNTIME_VARS_SANDBOX}"}}',
|
||||
},
|
||||
],
|
||||
contentGenerator: settings.contentGenerator,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,12 @@ export interface Settings {
|
||||
// Setting for setting maximum number of user/model/tool turns in a session.
|
||||
maxSessionTurns?: number;
|
||||
|
||||
// Setting for maximum token limit for conversation history before blocking requests
|
||||
sessionTokenLimit?: number;
|
||||
|
||||
// Setting for maximum number of files and folders to show in folder structure
|
||||
maxFolderItems?: number;
|
||||
|
||||
// A map of tool names to their summarization settings.
|
||||
summarizeToolOutput?: Record<string, SummarizeToolOutputSettings>;
|
||||
|
||||
@@ -109,6 +115,10 @@ export interface Settings {
|
||||
modelNames: string[];
|
||||
template: string;
|
||||
}>;
|
||||
contentGenerator?: {
|
||||
timeout?: number;
|
||||
maxRetries?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SettingsError {
|
||||
|
||||
@@ -69,7 +69,9 @@ export const createMockCommandContext = (
|
||||
byName: {},
|
||||
},
|
||||
},
|
||||
promptCount: 0,
|
||||
} as SessionStatsState,
|
||||
resetSession: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ describe('clearCommand', () => {
|
||||
expect(mockContext.ui.setDebugMessage).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(mockResetChat).toHaveBeenCalledTimes(1);
|
||||
expect(mockContext.session.resetSession).toHaveBeenCalledTimes(1);
|
||||
expect(uiTelemetryService.resetLastPromptTokenCount).toHaveBeenCalledTimes(
|
||||
1,
|
||||
);
|
||||
@@ -64,6 +65,8 @@ describe('clearCommand', () => {
|
||||
const setDebugMessageOrder = (mockContext.ui.setDebugMessage as Mock).mock
|
||||
.invocationCallOrder[0];
|
||||
const resetChatOrder = mockResetChat.mock.invocationCallOrder[0];
|
||||
const resetSessionOrder = (mockContext.session.resetSession as Mock).mock
|
||||
.invocationCallOrder[0];
|
||||
const resetTelemetryOrder = (
|
||||
uiTelemetryService.resetLastPromptTokenCount as Mock
|
||||
).mock.invocationCallOrder[0];
|
||||
@@ -73,6 +76,8 @@ describe('clearCommand', () => {
|
||||
expect(setDebugMessageOrder).toBeLessThan(resetChatOrder);
|
||||
expect(resetChatOrder).toBeLessThan(resetTelemetryOrder);
|
||||
expect(resetTelemetryOrder).toBeLessThan(clearOrder);
|
||||
expect(resetChatOrder).toBeLessThan(resetSessionOrder);
|
||||
expect(resetSessionOrder).toBeLessThan(clearOrder);
|
||||
});
|
||||
|
||||
it('should not attempt to reset chat if config service is not available', async () => {
|
||||
@@ -92,6 +97,7 @@ describe('clearCommand', () => {
|
||||
'Clearing terminal.',
|
||||
);
|
||||
expect(mockResetChat).not.toHaveBeenCalled();
|
||||
expect(nullConfigContext.session.resetSession).toHaveBeenCalledTimes(1);
|
||||
expect(uiTelemetryService.resetLastPromptTokenCount).toHaveBeenCalledTimes(
|
||||
1,
|
||||
);
|
||||
|
||||
@@ -24,6 +24,7 @@ export const clearCommand: SlashCommand = {
|
||||
}
|
||||
|
||||
uiTelemetryService.resetLastPromptTokenCount();
|
||||
context.session.resetSession();
|
||||
context.ui.clear();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,10 +15,10 @@ import { MessageType } from '../types.js';
|
||||
|
||||
export const docsCommand: SlashCommand = {
|
||||
name: 'docs',
|
||||
description: 'open full Gemini CLI documentation in your browser',
|
||||
description: 'open full Qwen Code documentation in your browser',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
action: async (context: CommandContext): Promise<void> => {
|
||||
const docsUrl = 'https://goo.gle/gemini-cli-docs';
|
||||
const docsUrl = 'https://github.com/QwenLM/qwen-code';
|
||||
|
||||
if (process.env.SANDBOX && process.env.SANDBOX !== 'sandbox-exec') {
|
||||
context.ui.addItem(
|
||||
|
||||
@@ -13,7 +13,7 @@ import { MessageType } from '../types.js';
|
||||
|
||||
export const toolsCommand: SlashCommand = {
|
||||
name: 'tools',
|
||||
description: 'list available Gemini CLI tools',
|
||||
description: 'list available Qwen Codetools',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
action: async (context: CommandContext, args?: string): Promise<void> => {
|
||||
const subCommand = args?.trim();
|
||||
@@ -40,7 +40,7 @@ export const toolsCommand: SlashCommand = {
|
||||
// Filter out MCP tools by checking for the absence of a serverName property
|
||||
const geminiTools = tools.filter((tool) => !('serverName' in tool));
|
||||
|
||||
let message = 'Available Gemini CLI tools:\n\n';
|
||||
let message = 'Available Qwen Code tools:\n\n';
|
||||
|
||||
if (geminiTools.length > 0) {
|
||||
geminiTools.forEach((tool) => {
|
||||
|
||||
@@ -63,6 +63,7 @@ export interface CommandContext {
|
||||
// Session-specific data
|
||||
session: {
|
||||
stats: SessionStatsState;
|
||||
resetSession: () => void;
|
||||
/** A transient list of shell commands the user has approved for this session. */
|
||||
sessionShellAllowlist: Set<string>;
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ interface SessionStatsContextValue {
|
||||
stats: SessionStatsState;
|
||||
startNewPrompt: () => void;
|
||||
getPromptCount: () => number;
|
||||
resetSession: () => void;
|
||||
}
|
||||
|
||||
// --- Context Definition ---
|
||||
@@ -109,13 +110,23 @@ export const SessionStatsProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
[stats.promptCount],
|
||||
);
|
||||
|
||||
const resetSession = useCallback(() => {
|
||||
setStats({
|
||||
sessionStartTime: new Date(),
|
||||
metrics: uiTelemetryService.getMetrics(),
|
||||
lastPromptTokenCount: uiTelemetryService.getLastPromptTokenCount(),
|
||||
promptCount: 0,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
stats,
|
||||
startNewPrompt,
|
||||
getPromptCount,
|
||||
resetSession,
|
||||
}),
|
||||
[stats, startNewPrompt, getPromptCount],
|
||||
[stats, startNewPrompt, getPromptCount, resetSession],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -162,6 +162,7 @@ export const useSlashCommandProcessor = (
|
||||
session: {
|
||||
stats: session.stats,
|
||||
sessionShellAllowlist,
|
||||
resetSession: session.resetSession,
|
||||
},
|
||||
}),
|
||||
[
|
||||
@@ -174,6 +175,7 @@ export const useSlashCommandProcessor = (
|
||||
clearItems,
|
||||
refreshStatic,
|
||||
session.stats,
|
||||
session.resetSession,
|
||||
onDebugMessage,
|
||||
pendingCompressionItemRef,
|
||||
setPendingCompressionItem,
|
||||
|
||||
@@ -511,6 +511,23 @@ export const useGeminiStream = (
|
||||
[addItem, config],
|
||||
);
|
||||
|
||||
const handleSessionTokenLimitExceededEvent = useCallback(
|
||||
(value: { currentTokens: number; limit: number; message: string }) =>
|
||||
addItem(
|
||||
{
|
||||
type: 'error',
|
||||
text:
|
||||
`🚫 Session token limit exceeded: ${value.currentTokens.toLocaleString()} tokens > ${value.limit.toLocaleString()} limit.\n\n` +
|
||||
`💡 Solutions:\n` +
|
||||
` • Start a new session: Use /clear command\n` +
|
||||
` • Increase limit: Add "sessionTokenLimit": (e.g., 128000) to your settings.json\n` +
|
||||
` • Compress history: Use /compress command to compress history`,
|
||||
},
|
||||
Date.now(),
|
||||
),
|
||||
[addItem],
|
||||
);
|
||||
|
||||
const handleLoopDetectedEvent = useCallback(() => {
|
||||
addItem(
|
||||
{
|
||||
@@ -560,6 +577,9 @@ export const useGeminiStream = (
|
||||
case ServerGeminiEventType.MaxSessionTurns:
|
||||
handleMaxSessionTurnsEvent();
|
||||
break;
|
||||
case ServerGeminiEventType.SessionTokenLimitExceeded:
|
||||
handleSessionTokenLimitExceededEvent(event.value);
|
||||
break;
|
||||
case ServerGeminiEventType.Finished:
|
||||
handleFinishedEvent(
|
||||
event as ServerGeminiFinishedEvent,
|
||||
@@ -591,6 +611,7 @@ export const useGeminiStream = (
|
||||
handleChatCompressionEvent,
|
||||
handleFinishedEvent,
|
||||
handleMaxSessionTurnsEvent,
|
||||
handleSessionTokenLimitExceededEvent,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ export async function checkForUpdates(): Promise<string | null> {
|
||||
notifier.update &&
|
||||
semver.gt(notifier.update.latest, notifier.update.current)
|
||||
) {
|
||||
return `Gemini CLI update available! ${notifier.update.current} → ${notifier.update.latest}\nRun npm install -g ${packageJson.name} to update`;
|
||||
return `Qwen Code update available! ${notifier.update.current} → ${notifier.update.latest}\nRun npm install -g ${packageJson.name} to update`;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -24,7 +24,7 @@ const homeDirectoryCheck: WarningCheck = {
|
||||
]);
|
||||
|
||||
if (workspaceRealPath === homeRealPath) {
|
||||
return 'You are running Gemini CLI in your home directory. It is recommended to run in a project-specific directory.';
|
||||
return 'You are running Qwen Code in your home directory. It is recommended to run in a project-specific directory.';
|
||||
}
|
||||
return null;
|
||||
} catch (_err: unknown) {
|
||||
|
||||
Reference in New Issue
Block a user