Skip to content
Closed
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
19 changes: 19 additions & 0 deletions packages/cli/src/gemini.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import { getUserStartupWarnings } from './utils/userStartupWarnings.js';
import { getCliVersion } from './utils/version.js';
import { writeStderrLine } from './utils/stdioHelpers.js';
import { computeWindowTitle } from './utils/windowTitle.js';
import { preconnectApi } from './utils/apiPreconnect.js';
import { startEarlyInputCapture } from './utils/earlyInputCapture.js';
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
import { showResumeSessionPicker } from './ui/components/StandaloneSessionPicker.js';
import { initializeLlmOutputLanguage } from './utils/languageUtils.js';
Expand Down Expand Up @@ -212,6 +214,7 @@ export async function startInteractiveUI(
export async function main() {
setupUnhandledRejectionHandler();
const settings = loadSettings();

await cleanupCheckpoints();

let argv = await parseArguments();
Expand Down Expand Up @@ -366,6 +369,19 @@ export async function main() {
// This ensures MCP server subprocesses are properly terminated on exit
registerCleanup(() => config.shutdown());

// Startup optimization: preconnect API to warm TCP+TLS connection
// Only fire for flows that will make API calls
try {
const authType = config.getModelsConfig().getCurrentAuthType();
preconnectApi(authType, {
settingsBaseUrl: settings.merged.security?.auth?.baseUrl as
| string
| undefined,
});
} catch {
// If we can't get authType, skip preconnect - it's optional optimization
}

// FIXME: list extensions after the config initialize
// if (config.getListExtensions()) {
// console.log('Installed extensions:');
Expand All @@ -382,6 +398,9 @@ export async function main() {
// input showing up in the output.
process.stdin.setRawMode(true);

// Startup optimization: start early input capture
startEarlyInputCapture();

// This cleanup isn't strictly needed but may help in certain situations.
process.on('SIGTERM', () => {
process.stdin.setRawMode(wasRaw);
Expand Down
24 changes: 24 additions & 0 deletions packages/cli/src/ui/contexts/KeypressContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ import {
import { clipboardHasImage } from '../utils/clipboardUtils.js';

import { FOCUS_IN, FOCUS_OUT } from '../hooks/useFocus.js';
import {
stopEarlyInputCapture,
getAndClearCapturedInput,
} from '../../utils/earlyInputCapture.js';

const ESC = '\u001B';
export const PASTE_MODE_PREFIX = `${ESC}[200~`;
Expand Down Expand Up @@ -158,6 +162,10 @@ export function KeypressProvider({
setRawMode(true);
}

// Startup optimization: stop early input capture and get captured input
stopEarlyInputCapture();
const capturedInput = getAndClearCapturedInput();

const keypressStream = new PassThrough();
let usePassthrough = false;
// Use passthrough mode when pasteWorkaround is enabled,
Expand Down Expand Up @@ -985,6 +993,22 @@ export function KeypressProvider({
stdin.on('keypress', handleKeypress);
}

// Startup optimization: replay captured input if available
if (capturedInput.length > 0) {
debugLogger.debug(
`Replaying ${capturedInput.length} bytes of captured input`,
);
// Process in next event loop tick to ensure subscribers are ready
setImmediate(() => {
if (usePassthrough) {
keypressStream.write(capturedInput);
} else {
// Emit data event directly on stdin
stdin.emit('data', capturedInput);
}
});
}

return () => {
if (usePassthrough) {
keypressStream.removeListener('keypress', handleKeypress);
Expand Down
163 changes: 163 additions & 0 deletions packages/cli/src/utils/apiPreconnect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { preconnectApi, resetPreconnectState } from './apiPreconnect.js';

// Mock fetch
const mockFetch = vi.fn().mockResolvedValue(undefined);
global.fetch = mockFetch;

describe('apiPreconnect', () => {
beforeEach(() => {
resetPreconnectState();
mockFetch.mockClear();
mockFetch.mockResolvedValue(undefined);
delete process.env['HTTPS_PROXY'];
delete process.env['https_proxy'];
delete process.env['HTTP_PROXY'];
delete process.env['http_proxy'];
delete process.env['OPENAI_BASE_URL'];
delete process.env['ANTHROPIC_BASE_URL'];
delete process.env['GEMINI_BASE_URL'];
delete process.env['QWEN_CODE_DISABLE_PRECONNECT'];
delete process.env['NODE_EXTRA_CA_CERTS'];
delete process.env['SANDBOX'];
});

afterEach(() => {
vi.restoreAllMocks();
});

describe('shouldSkipPreconnect', () => {
it('should skip when HTTPS_PROXY is set', () => {
process.env['HTTPS_PROXY'] = 'http://proxy.example.com:8080';
preconnectApi('qwen-oauth');
expect(mockFetch).not.toHaveBeenCalled();
});

it('should skip when https_proxy is set', () => {
process.env['https_proxy'] = 'http://proxy.example.com:8080';
preconnectApi('qwen-oauth');
expect(mockFetch).not.toHaveBeenCalled();
});

it('should skip when HTTP_PROXY is set', () => {
process.env['HTTP_PROXY'] = 'http://proxy.example.com:8080';
preconnectApi('qwen-oauth');
expect(mockFetch).not.toHaveBeenCalled();
});

it('should skip when http_proxy is set', () => {
process.env['http_proxy'] = 'http://proxy.example.com:8080';
preconnectApi('qwen-oauth');
expect(mockFetch).not.toHaveBeenCalled();
});

it('should skip when NODE_EXTRA_CA_CERTS is set', () => {
process.env['NODE_EXTRA_CA_CERTS'] = '/path/to/ca.pem';
preconnectApi('qwen-oauth');
expect(mockFetch).not.toHaveBeenCalled();
});

it('should skip when custom baseUrl is set', () => {
preconnectApi('openai', { settingsBaseUrl: 'https://custom.api.com/v1' });
expect(mockFetch).not.toHaveBeenCalled();
});

it('should not skip when baseUrl is a default URL', () => {
preconnectApi('openai', { settingsBaseUrl: 'https://api.openai.com/v1' });
expect(mockFetch).toHaveBeenCalled();
});
});

describe('preconnect behavior', () => {
it('should use default baseUrl for qwen-oauth', () => {
preconnectApi('qwen-oauth');
expect(mockFetch).toHaveBeenCalledWith(
'https://coding.dashscope.aliyuncs.com',
expect.objectContaining({ method: 'HEAD' }),
);
});

it('should use default baseUrl for openai', () => {
preconnectApi('openai');
expect(mockFetch).toHaveBeenCalledWith(
'https://api.openai.com',
expect.objectContaining({ method: 'HEAD' }),
);
});

it('should use default baseUrl for anthropic', () => {
preconnectApi('anthropic');
expect(mockFetch).toHaveBeenCalledWith(
'https://api.anthropic.com',
expect.objectContaining({ method: 'HEAD' }),
);
});

it('should use settings baseUrl when available', () => {
preconnectApi('openai', {
settingsBaseUrl: 'https://custom.openai.com/v1',
});
// Should skip because it's a custom URL
expect(mockFetch).not.toHaveBeenCalled();
});

it('should use environment variable baseUrl when available', () => {
process.env['OPENAI_BASE_URL'] = 'https://custom.env.com/v1';
preconnectApi('openai');
// Should skip because it's a custom URL
expect(mockFetch).not.toHaveBeenCalled();
});

it('should only check OPENAI_BASE_URL for openai authType', () => {
process.env['OPENAI_BASE_URL'] = 'https://api.openai.com/v1';
process.env['ANTHROPIC_BASE_URL'] = 'https://custom.anthropic.com/v1';
preconnectApi('openai');
// Should use OPENAI_BASE_URL (which is default), ignore ANTHROPIC_BASE_URL
expect(mockFetch).toHaveBeenCalledWith(
'https://api.openai.com/v1',
expect.objectContaining({ method: 'HEAD' }),
);
});

it('should only check ANTHROPIC_BASE_URL for anthropic authType', () => {
process.env['ANTHROPIC_BASE_URL'] = 'https://api.anthropic.com';
process.env['OPENAI_BASE_URL'] = 'https://custom.openai.com/v1';
preconnectApi('anthropic');
// Should use ANTHROPIC_BASE_URL (which is default), ignore OPENAI_BASE_URL
expect(mockFetch).toHaveBeenCalledWith(
'https://api.anthropic.com',
expect.objectContaining({ method: 'HEAD' }),
);
});

it('should not fire twice', () => {
preconnectApi('qwen-oauth');
preconnectApi('openai');
expect(mockFetch).toHaveBeenCalledTimes(1);
});

it('should handle fetch errors gracefully', async () => {
mockFetch.mockRejectedValue(new Error('Network error'));
// Should not throw
expect(() => preconnectApi('qwen-oauth')).not.toThrow();
});

it('should skip when QWEN_CODE_DISABLE_PRECONNECT is set', () => {
process.env['QWEN_CODE_DISABLE_PRECONNECT'] = '1';
preconnectApi('qwen-oauth');
expect(mockFetch).not.toHaveBeenCalled();
});

it('should skip in sandbox mode', () => {
process.env['SANDBOX'] = '1';
preconnectApi('qwen-oauth');
expect(mockFetch).not.toHaveBeenCalled();
});
});
});
Loading
Loading