Skip to content

Commit 1bd7e6a

Browse files
committed
perf: reuse OpenAI client instance and add connection warmup
Cache the OpenAI client at module level keyed by (apiKey, baseURL) to avoid creating a fresh HTTP connection pool on every LLM turn. The client is a stateless fetch wrapper so sharing across calls is safe. Model, thinking-mode and other settings are still read fresh from config files each time. Also add a mount-time warmup effect that eagerly creates the client so the TCP+TLS connection is established while the user composes their first prompt.
1 parent 3a6ec19 commit 1bd7e6a

2 files changed

Lines changed: 40 additions & 2 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ dist/
55
.vscode/
66
*.tgz
77
*.log
8+
scripts/

src/ui/App.tsx

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@ export function App({ projectRoot, initialPrompt, onRestart }: AppProps): React.
162162
void refreshSkills();
163163
}, [refreshSessionsList, refreshSkills]);
164164

165+
// Eagerly create the OpenAI client on mount so the TCP+TLS connection
166+
// warmup (fire-and-forget inside createOpenAIClient) starts before the
167+
// user sends their first prompt.
168+
useEffect(() => {
169+
createOpenAIClient(projectRoot);
170+
}, [projectRoot]);
171+
165172
useLayoutEffect(() => {
166173
const settings = resolveCurrentSettings(projectRoot);
167174
void sessionManager.initMcpServers(settings.mcpServers);
@@ -721,6 +728,13 @@ export function resolveCurrentSettings(projectRoot: string = process.cwd()): Res
721728
);
722729
}
723730

731+
// Module-level cache for the OpenAI client instance. The client itself is
732+
// a stateless fetch wrapper, so it is safe to share across calls as long as
733+
// the apiKey + baseURL stay the same. Model, thinking-mode and other
734+
// settings are always read fresh from the project / user config files.
735+
let _cachedOpenAI: OpenAI | null = null;
736+
let _cachedOpenAIKey = "";
737+
724738
export function createOpenAIClient(projectRoot: string = process.cwd()): {
725739
client: OpenAI | null;
726740
model: string;
@@ -749,12 +763,35 @@ export function createOpenAIClient(projectRoot: string = process.cwd()): {
749763
};
750764
}
751765

752-
const client = new OpenAI({
766+
const cacheKey = `${settings.apiKey}::${settings.baseURL}`;
767+
if (_cachedOpenAI && _cachedOpenAIKey === cacheKey) {
768+
return {
769+
client: _cachedOpenAI,
770+
model: settings.model,
771+
baseURL: settings.baseURL,
772+
thinkingEnabled: settings.thinkingEnabled,
773+
reasoningEffort: settings.reasoningEffort,
774+
debugLogEnabled: settings.debugLogEnabled,
775+
notify: settings.notify,
776+
webSearchTool: settings.webSearchTool,
777+
env: settings.env,
778+
machineId: getMachineId(),
779+
};
780+
}
781+
782+
_cachedOpenAI = new OpenAI({
753783
apiKey: settings.apiKey,
754784
baseURL: settings.baseURL || undefined,
755785
});
786+
_cachedOpenAIKey = cacheKey;
787+
788+
// Fire-and-forget warmup: pre-establish TCP+TLS connection to the API
789+
// server while the user is composing their first prompt. Errors are
790+
// silently ignored — the real request will retry on its own if needed.
791+
void _cachedOpenAI.models.list().catch(() => {});
792+
756793
return {
757-
client,
794+
client: _cachedOpenAI,
758795
model: settings.model,
759796
baseURL: settings.baseURL,
760797
thinkingEnabled: settings.thinkingEnabled,

0 commit comments

Comments
 (0)