From ed28d9342b34bd01f5da82f57a843bb193b51138 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Mon, 21 Sep 2026 18:55:19 +0100 Subject: [PATCH] perf: parallelize new-session model discovery --- src/CodexAcpClient.ts | 16 +++++----- .../CodexACPAgent/CodexAcpClient.test.ts | 31 ++++++++++++++++++- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/CodexAcpClient.ts b/src/CodexAcpClient.ts index 8a5af9e7..1a8cfe46 100644 --- a/src/CodexAcpClient.ts +++ b/src/CodexAcpClient.ts @@ -600,13 +600,15 @@ export class CodexAcpClient { const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta); await this.refreshSkills(request.cwd, additionalDirectories); - const response = await this.codexClient.threadStart({ - config: await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers), - modelProvider: this.getModelProvider(), - cwd: request.cwd, - }); - - const codexModels = await this.fetchAvailableModels(); + const config = await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers); + const [response, codexModels] = await Promise.all([ + this.codexClient.threadStart({ + config, + modelProvider: this.getModelProvider(), + cwd: request.cwd, + }), + this.fetchAvailableModels(), + ]); if (codexModels.length === 0) { throw new Error("Codex did not return any models"); } diff --git a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts index 77aa7af7..ed1e3d1d 100644 --- a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts +++ b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts @@ -98,8 +98,8 @@ describe('ACP server test', { timeout: 40_000 }, () => { "account/read", "thread/start", "model/list", - "thread/started", "account/read", + "thread/started", "skills/list", ]); expect(loginRequest).toEqual({ @@ -475,6 +475,35 @@ describe('ACP server test', { timeout: 40_000 }, () => { expect(listSkillsSpy.mock.invocationCallOrder[0]!).toBeLessThan(threadStartSpy.mock.invocationCallOrder[0]!); }); + it('fetches models while a new thread is starting', async () => { + const mockFixture = createCodexMockTestFixture(); + const codexAcpClient = mockFixture.getCodexAcpClient(); + const codexAppServerClient = mockFixture.getCodexAppServerClient(); + const threadStart = deferred>>(); + + vi.spyOn(codexAppServerClient, "listSkills").mockResolvedValue({data: []}); + vi.spyOn(codexAppServerClient, "threadStart").mockReturnValue(threadStart.promise); + const listModelsSpy = vi.spyOn(codexAppServerClient, "listModels").mockResolvedValue({ + data: [createTestModel({id: "gpt-5"})], + nextCursor: null, + }); + + const session = codexAcpClient.newSession({cwd: "/workspace", mcpServers: []}); + await vi.waitFor(() => expect(listModelsSpy).toHaveBeenCalledOnce()); + + threadStart.resolve({ + thread: {id: "thread-id"} as any, + model: "gpt-5", + reasoningEffort: "medium", + serviceTier: null, + } as Awaited>); + + await expect(session).resolves.toMatchObject({ + sessionId: "thread-id", + currentModelId: "gpt-5[medium]", + }); + }); + it('prefers ACP additional directories over legacy meta roots for new session skill discovery', async () => { const mockFixture = createCodexMockTestFixture(); const codexAcpClient = mockFixture.getCodexAcpClient();