perf: lazy codex app server test imports

This commit is contained in:
Peter Steinberger
2026-04-24 03:41:55 +01:00
parent d3f6783b16
commit 913f97c956
6 changed files with 47 additions and 43 deletions

View File

@@ -8,6 +8,12 @@ import { type CodexAppServerClientFactory } from "./src/app-server/client-factor
import type { CodexAppServerClient } from "./src/app-server/client.js";
import { resolveCodexAppServerRuntimeOptions } from "./src/app-server/config.js";
import { readModelListResult } from "./src/app-server/models.js";
import {
assertCodexThreadStartResponse,
assertCodexTurnStartResponse,
readCodexErrorNotification,
readCodexTurnCompletedNotification,
} from "./src/app-server/protocol-validators.js";
import {
isJsonObject,
type CodexServerNotification,
@@ -17,13 +23,6 @@ import {
type CodexTurnStartParams,
type JsonObject,
} from "./src/app-server/protocol.js";
import {
assertCodexThreadStartResponse,
assertCodexTurnStartResponse,
readCodexErrorNotification,
readCodexTurnCompletedNotification,
} from "./src/app-server/protocol-validators.js";
import { createIsolatedCodexAppServerClient } from "./src/app-server/shared-client.js";
const DEFAULT_CODEX_IMAGE_MODEL =
FALLBACK_CODEX_MODELS.find((model) => model.inputModalities.includes("image"))?.id ??
@@ -83,11 +82,14 @@ async function describeCodexImages(
const ownsClient = !options.clientFactory;
const client = options.clientFactory
? await options.clientFactory(appServer.start, req.profile)
: await createIsolatedCodexAppServerClient({
startOptions: appServer.start,
timeoutMs,
authProfileId: req.profile,
});
: await import("./src/app-server/shared-client.js").then(
({ createIsolatedCodexAppServerClient }) =>
createIsolatedCodexAppServerClient({
startOptions: appServer.start,
timeoutMs,
authProfileId: req.profile,
}),
);
const abortController = new AbortController();
const timeout = setTimeout(() => abortController.abort("timeout"), timeoutMs);
timeout.unref?.();
@@ -229,7 +231,8 @@ function createCodexImageTurnCollector(threadId: string) {
return;
}
if (notification.method === "turn/completed") {
completedTurn = readCodexTurnCompletedNotification(notification.params)?.turn ?? completedTurn;
completedTurn =
readCodexTurnCompletedNotification(notification.params)?.turn ?? completedTurn;
resolveCompletion?.();
return;
}

View File

@@ -1,6 +1,5 @@
import type { CodexAppServerClient } from "./client.js";
import type { CodexAppServerStartOptions } from "./config.js";
import { getSharedCodexAppServerClient } from "./shared-client.js";
export type CodexAppServerClientFactory = (
startOptions?: CodexAppServerStartOptions,
@@ -10,7 +9,10 @@ export type CodexAppServerClientFactory = (
export const defaultCodexAppServerClientFactory: CodexAppServerClientFactory = (
startOptions,
authProfileId,
) => getSharedCodexAppServerClient({ startOptions, authProfileId });
) =>
import("./shared-client.js").then(({ getSharedCodexAppServerClient }) =>
getSharedCodexAppServerClient({ startOptions, authProfileId }),
);
export function createCodexAppServerClientFactoryTestHooks(
setFactory: (factory: CodexAppServerClientFactory) => void,

View File

@@ -1,10 +1,6 @@
import type { CodexAppServerStartOptions } from "./config.js";
import type { v2 } from "./protocol-generated/typescript/index.js";
import { readCodexModelListResponse } from "./protocol-validators.js";
import {
createIsolatedCodexAppServerClient,
getSharedCodexAppServerClient,
} from "./shared-client.js";
export type CodexAppServerModel = {
id: string;
@@ -38,6 +34,8 @@ export async function listCodexAppServerModels(
): Promise<CodexAppServerModelListResult> {
const timeoutMs = options.timeoutMs ?? 2500;
const useSharedClient = options.sharedClient !== false;
const { createIsolatedCodexAppServerClient, getSharedCodexAppServerClient } =
await import("./shared-client.js");
const client = useSharedClient
? await getSharedCodexAppServerClient({
startOptions: options.startOptions,

View File

@@ -32,6 +32,10 @@ import { resolveCodexAppServerRuntimeOptions } from "./config.js";
import { createCodexDynamicToolBridge } from "./dynamic-tools.js";
import { handleCodexAppServerElicitationRequest } from "./elicitation-bridge.js";
import { CodexAppServerEventProjector } from "./event-projector.js";
import {
assertCodexTurnStartResponse,
readCodexDynamicToolCallParams,
} from "./protocol-validators.js";
import {
isJsonObject,
type CodexServerNotification,
@@ -40,10 +44,6 @@ import {
type JsonObject,
type JsonValue,
} from "./protocol.js";
import {
assertCodexTurnStartResponse,
readCodexDynamicToolCallParams,
} from "./protocol-validators.js";
import { readCodexAppServerBinding, type CodexAppServerThreadBinding } from "./session-binding.js";
import { clearSharedCodexAppServerClient } from "./shared-client.js";
import {
@@ -58,6 +58,7 @@ import {
recordCodexTrajectoryContext,
} from "./trajectory.js";
import { mirrorCodexAppServerTranscript } from "./transcript-mirror.js";
import { filterToolsForVisionInputs } from "./vision-tools.js";
let clientFactory = defaultCodexAppServerClientFactory;
@@ -601,19 +602,6 @@ async function buildDynamicTools(input: DynamicToolBuildParams) {
});
}
function filterToolsForVisionInputs<T extends { name?: string }>(
tools: T[],
params: {
modelHasVision: boolean;
hasInboundImages: boolean;
},
): T[] {
if (!params.modelHasVision || !params.hasInboundImages) {
return tools;
}
return tools.filter((tool) => tool.name !== "image");
}
async function withCodexStartupTimeout<T>(params: {
timeoutMs: number;
timeoutFloorMs?: number;

View File

@@ -1,14 +1,15 @@
import { describe, expect, it } from "vitest";
import { __testing } from "./run-attempt.js";
import { filterToolsForVisionInputs } from "./vision-tools.js";
describe("Codex dynamic tool filtering", () => {
it("drops the image tool when the model already has inbound vision input", () => {
const toolNames = __testing
.filterToolsForVisionInputs([{ name: "image" }, { name: "read" }, { name: "write" }], {
const toolNames = filterToolsForVisionInputs(
[{ name: "image" }, { name: "read" }, { name: "write" }],
{
modelHasVision: true,
hasInboundImages: true,
})
.map((tool) => tool.name);
},
).map((tool) => tool.name);
expect(toolNames).toContain("read");
expect(toolNames).toContain("write");
@@ -19,13 +20,13 @@ describe("Codex dynamic tool filtering", () => {
const tools = [{ name: "image" }, { name: "read" }];
expect(
__testing.filterToolsForVisionInputs(tools, {
filterToolsForVisionInputs(tools, {
modelHasVision: false,
hasInboundImages: true,
}),
).toBe(tools);
expect(
__testing.filterToolsForVisionInputs(tools, {
filterToolsForVisionInputs(tools, {
modelHasVision: true,
hasInboundImages: false,
}),

View File

@@ -0,0 +1,12 @@
export function filterToolsForVisionInputs<T extends { name?: string }>(
tools: T[],
params: {
modelHasVision: boolean;
hasInboundImages: boolean;
},
): T[] {
if (!params.modelHasVision || !params.hasInboundImages) {
return tools;
}
return tools.filter((tool) => tool.name !== "image");
}