Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ curl -qL https://www.npmjs.com/install.sh | sh
### Installation

```bash
npm install -g @qwen-code/qwen-code
npm install -g @qwen-code/qwen-code@latest
qwen --version
```

Expand All @@ -45,6 +45,17 @@ npm install
npm install -g .
```

We now support max session token limit, you can set it in your `.qwen/settings.json` file to save the token usage.
For example, if you want to set the max session token limit to 32000, you can set it like this:

```json
{
"maxSessionToken": 32000
}
```

The max session means the maximum number of tokens that can be used in one chat (not the total usage during multiple tool call shoots); if you reach the limit, you can use the `/compress` command to compress the history and go on, or use `/clear` command to clear the history.

### API Configuration

Set your Qwen API key (In Qwen Code project, you can also set your API key in `.env` file). the `.env` file should be placed in the root directory of your current project.
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/cli/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ export async function loadCliConfig(
model: argv.model!,
extensionContextFilePaths,
maxSessionTurns: settings.maxSessionTurns ?? -1,
sessionTokenLimit: settings.sessionTokenLimit ?? 32000,
maxFolderItems: settings.maxFolderItems ?? 20,
listExtensions: argv.listExtensions || false,
activeExtensions: activeExtensions.map((e) => ({
name: e.config.name,
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/config/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,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;

// Sampling parameters for content generation
sampling_params?: {
top_p?: number;
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/test-utils/mockCommandContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ export const createMockCommandContext = (
byName: {},
},
},
promptCount: 0,
} as SessionStatsState,
resetSession: vi.fn(),
},
};

Expand Down
8 changes: 7 additions & 1 deletion packages/cli/src/ui/commands/clearCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,22 @@ describe('clearCommand', () => {

expect(mockResetChat).toHaveBeenCalledTimes(1);

expect(mockContext.session.resetSession).toHaveBeenCalledTimes(1);

expect(mockContext.ui.clear).toHaveBeenCalledTimes(1);

// Check the order of operations.
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 clearOrder = (mockContext.ui.clear as Mock).mock
.invocationCallOrder[0];

expect(setDebugMessageOrder).toBeLessThan(resetChatOrder);
expect(resetChatOrder).toBeLessThan(clearOrder);
expect(resetChatOrder).toBeLessThan(resetSessionOrder);
expect(resetSessionOrder).toBeLessThan(clearOrder);
});

it('should not attempt to reset chat if config service is not available', async () => {
Expand All @@ -73,6 +78,7 @@ describe('clearCommand', () => {
'Clearing terminal and resetting chat.',
);
expect(mockResetChat).not.toHaveBeenCalled();
expect(nullConfigContext.session.resetSession).toHaveBeenCalledTimes(1);
expect(nullConfigContext.ui.clear).toHaveBeenCalledTimes(1);
});
});
1 change: 1 addition & 0 deletions packages/cli/src/ui/commands/clearCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const clearCommand: SlashCommand = {
action: async (context, _args) => {
context.ui.setDebugMessage('Clearing terminal and resetting chat.');
await context.services.config?.getGeminiClient()?.resetChat();
context.session.resetSession();
context.ui.clear();
},
};
1 change: 1 addition & 0 deletions packages/cli/src/ui/commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface CommandContext {
// Session-specific data
session: {
stats: SessionStatsState;
resetSession: () => void;
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/ui/components/AboutBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const AboutBox: React.FC<AboutBoxProps> = ({
>
<Box marginBottom={1}>
<Text bold color={Colors.AccentPurple}>
About Gemini CLI
About Qwen Code
</Text>
</Box>
<Box flexDirection="row">
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe('<HistoryItemDisplay />', () => {
const { lastFrame } = render(
<HistoryItemDisplay {...baseItem} item={item} />,
);
expect(lastFrame()).toContain('About Gemini CLI');
expect(lastFrame()).toContain('About Qwen Code');
});

it('renders ModelStatsDisplay for "model_stats" type', () => {
Expand Down
13 changes: 12 additions & 1 deletion packages/cli/src/ui/contexts/SessionContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ interface SessionStatsContextValue {
stats: SessionStatsState;
startNewPrompt: () => void;
getPromptCount: () => number;
resetSession: () => void;
}

// --- Context Definition ---
Expand Down Expand Up @@ -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 (
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/ui/hooks/slashCommandProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export const useSlashCommandProcessor = (
},
session: {
stats: session.stats,
resetSession: session.resetSession,
},
}),
[
Expand All @@ -183,6 +184,7 @@ export const useSlashCommandProcessor = (
clearItems,
refreshStatic,
session.stats,
session.resetSession,
onDebugMessage,
],
);
Expand Down Expand Up @@ -538,7 +540,7 @@ export const useSlashCommandProcessor = (
// Filter out MCP tools by checking if they have 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) => {
Expand Down
21 changes: 21 additions & 0 deletions packages/cli/src/ui/hooks/useGeminiStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,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(
{
Expand Down Expand Up @@ -501,6 +518,9 @@ export const useGeminiStream = (
case ServerGeminiEventType.MaxSessionTurns:
handleMaxSessionTurnsEvent();
break;
case ServerGeminiEventType.SessionTokenLimitExceeded:
handleSessionTokenLimitExceededEvent(event.value);
break;
case ServerGeminiEventType.LoopDetected:
// handle later because we want to move pending history to history
// before we add loop detected message to history
Expand All @@ -525,6 +545,7 @@ export const useGeminiStream = (
scheduleToolCalls,
handleChatCompressionEvent,
handleMaxSessionTurnsEvent,
handleSessionTokenLimitExceededEvent,
],
);

Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"shell-quote": "^1.8.3",
"simple-git": "^3.28.0",
"strip-ansi": "^7.1.0",
"tiktoken": "^1.0.21",
"undici": "^7.10.0",
"ws": "^8.18.0"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/code_assist/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export interface HttpOptions {
headers?: Record<string, string>;
}

export const CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com';
export const CODE_ASSIST_ENDPOINT = 'https://localhost:0'; // Disable Google Code Assist API Request
export const CODE_ASSIST_API_VERSION = 'v1internal';

export class CodeAssistServer implements ContentGenerator {
Expand Down
16 changes: 15 additions & 1 deletion packages/core/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ export interface ConfigParameters {
model: string;
extensionContextFilePaths?: string[];
maxSessionTurns?: number;
sessionTokenLimit?: number;
maxFolderItems?: number;
listExtensions?: boolean;
activeExtensions?: ActiveExtension[];
noBrowser?: boolean;
Expand Down Expand Up @@ -216,6 +218,8 @@ export class Config {
}>;
private modelSwitchedDuringSession: boolean = false;
private readonly maxSessionTurns: number;
private readonly sessionTokenLimit: number;
private readonly maxFolderItems: number;
private readonly listExtensions: boolean;
private readonly _activeExtensions: ActiveExtension[];
flashFallbackHandler?: FlashFallbackHandler;
Expand Down Expand Up @@ -262,6 +266,8 @@ export class Config {
this.model = params.model;
this.extensionContextFilePaths = params.extensionContextFilePaths ?? [];
this.maxSessionTurns = params.maxSessionTurns ?? -1;
this.sessionTokenLimit = params.sessionTokenLimit ?? 32000;
this.maxFolderItems = params.maxFolderItems ?? 20;
this.listExtensions = params.listExtensions ?? false;
this._activeExtensions = params.activeExtensions ?? [];
this.noBrowser = params.noBrowser ?? false;
Expand Down Expand Up @@ -353,6 +359,14 @@ export class Config {
return this.maxSessionTurns;
}

getSessionTokenLimit(): number {
return this.sessionTokenLimit;
}

getMaxFolderItems(): number {
return this.maxFolderItems;
}

setQuotaErrorOccurred(value: boolean): void {
this.quotaErrorOccurred = value;
}
Expand Down Expand Up @@ -516,7 +530,7 @@ export class Config {
}

getUsageStatisticsEnabled(): boolean {
return this.usageStatisticsEnabled;
return false; // 禁用遥测统计,防止网络请求
}

getExtensionContextFilePaths(): string[] {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/config/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

export const DEFAULT_GEMINI_MODEL = 'qwen3-coder-max';
export const DEFAULT_GEMINI_MODEL = 'qwen3-coder-plus';
export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-2.5-flash';
export const DEFAULT_GEMINI_EMBEDDING_MODEL = 'gemini-embedding-001';
Loading