




















@@ -526,6 +526,35 @@ function createNamedDynamicTool(
526526};
527527}
528528529+async function buildDynamicToolsForTest(
530+params: EmbeddedRunAttemptParams,
531+workspaceDir: string,
532+options: Partial<
533+Pick<
534+Parameters<typeof testing.buildDynamicTools>[0],
535+"forceHeartbeatTool" | "ignoreRuntimePlan"
536+>
537+> = {},
538+) {
539+const sandboxSessionKey = params.sessionKey;
540+if (!sandboxSessionKey) {
541+throw new Error("createParams must provide a sessionKey for Codex dynamic tool tests.");
542+}
543+return testing.buildDynamicTools({
544+ params,
545+resolvedWorkspace: workspaceDir,
546+effectiveWorkspace: workspaceDir,
547+ sandboxSessionKey,
548+sandbox: { enabled: false, backendId: "docker" } as never,
549+nativeToolSurfaceEnabled: true,
550+runAbortController: new AbortController(),
551+sessionAgentId: "main",
552+pluginConfig: {},
553+onYieldDetected: () => undefined,
554+ ...options,
555+});
556+}
557+529558type RuntimeDynamicToolForTest = Parameters<
530559typeof createCodexDynamicToolBridge
531560>[0]["tools"][number];
@@ -2236,34 +2265,19 @@ describe("runCodexAppServerAttempt", () => {
22362265createRuntimeDynamicTool("message"),
22372266createRuntimeDynamicTool("music_generate"),
22382267]);
2239-const harness = createStartedThreadHarness(async (method) => {
2240-if (method === "turn/start") {
2241-await new Promise((resolve) => setTimeout(resolve, 5));
2242-return turnStartResult();
2243-}
2244-return undefined;
2245-});
2246-const params = createParams(
2247-path.join(tempDir, "session.jsonl"),
2248-path.join(tempDir, "workspace"),
2249-);
2268+const workspaceDir = path.join(tempDir, "workspace");
2269+const params = createParams(path.join(tempDir, "session.jsonl"), workspaceDir);
22502270params.disableTools = false;
22512271params.runtimePlan = createCodexRuntimePlanFixture();
22522272params.sourceReplyDeliveryMode = "message_tool_only";
22532273params.toolsAllow = ["music_generate"];
225422742255-const run = runCodexAppServerAttempt(params, {
2256-pluginConfig: { appServer: { mode: "yolo" } },
2257-});
2258-await harness.waitForMethod("turn/start", 120_000);
2259-await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
2260-await run;
2261-2262-const startRequest = harness.requests.find((entry) => entry.method === "thread/start");
2263-const dynamicToolNames =
2264-(
2265-startRequest?.params as { dynamicTools?: Array<{ name?: string }> } | undefined
2266-)?.dynamicTools?.map((tool) => tool.name) ?? [];
2275+const dynamicToolNames = (
2276+await buildDynamicToolsForTest(params, workspaceDir, {
2277+forceHeartbeatTool: true,
2278+ignoreRuntimePlan: true,
2279+})
2280+).map((tool) => tool.name);
2267228122682282expect(dynamicToolNames).toContain("message");
22692283expect(dynamicToolNames).toContain("music_generate");
@@ -2277,28 +2291,19 @@ describe("runCodexAppServerAttempt", () => {
22772291 ? [createRuntimeDynamicTool("heartbeat_respond")]
22782292 : []),
22792293]);
2280-const harness = createStartedThreadHarness();
2281-const params = createParams(
2282-path.join(tempDir, "session.jsonl"),
2283-path.join(tempDir, "workspace"),
2284-);
2294+const workspaceDir = path.join(tempDir, "workspace");
2295+const params = createParams(path.join(tempDir, "session.jsonl"), workspaceDir);
22852296params.disableTools = false;
22862297params.runtimePlan = createCodexRuntimePlanFixture();
22872298params.sourceReplyDeliveryMode = "message_tool_only";
22882299params.toolsAllow = [];
228923002290-const run = runCodexAppServerAttempt(params, {
2291-pluginConfig: { appServer: { mode: "yolo" } },
2292-});
2293-await harness.waitForMethod("turn/start", 120_000);
2294-await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
2295-await run;
2296-2297-const startRequest = harness.requests.find((entry) => entry.method === "thread/start");
2298-const dynamicToolNames =
2299-(
2300-startRequest?.params as { dynamicTools?: Array<{ name?: string }> } | undefined
2301-)?.dynamicTools?.map((tool) => tool.name) ?? [];
2301+const dynamicToolNames = (
2302+await buildDynamicToolsForTest(params, workspaceDir, {
2303+forceHeartbeatTool: true,
2304+ignoreRuntimePlan: true,
2305+})
2306+).map((tool) => tool.name);
2302230723032308expect(dynamicToolNames).toEqual(["message"]);
23042309});
@@ -2310,27 +2315,18 @@ describe("runCodexAppServerAttempt", () => {
23102315 ? [createRuntimeDynamicTool("heartbeat_respond")]
23112316 : []),
23122317]);
2313-const harness = createStartedThreadHarness();
2314-const params = createParams(
2315-path.join(tempDir, "session.jsonl"),
2316-path.join(tempDir, "workspace"),
2317-);
2318+const workspaceDir = path.join(tempDir, "workspace");
2319+const params = createParams(path.join(tempDir, "session.jsonl"), workspaceDir);
23182320params.disableTools = false;
23192321params.runtimePlan = createCodexRuntimePlanFixture();
23202322params.toolsAllow = ["message"];
232123232322-const run = runCodexAppServerAttempt(params, {
2323-pluginConfig: { appServer: { mode: "yolo" } },
2324-});
2325-await harness.waitForMethod("turn/start", 120_000);
2326-await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
2327-await run;
2328-2329-const startRequest = harness.requests.find((entry) => entry.method === "thread/start");
2330-const dynamicToolNames =
2331-(
2332-startRequest?.params as { dynamicTools?: Array<{ name?: string }> } | undefined
2333-)?.dynamicTools?.map((tool) => tool.name) ?? [];
2324+const dynamicToolNames = (
2325+await buildDynamicToolsForTest(params, workspaceDir, {
2326+forceHeartbeatTool: true,
2327+ignoreRuntimePlan: true,
2328+})
2329+).map((tool) => tool.name);
2334233023352331expect(dynamicToolNames).toEqual(["message"]);
23362332});
@@ -2343,33 +2339,50 @@ describe("runCodexAppServerAttempt", () => {
23432339createRuntimeDynamicTool("sessions_spawn"),
23442340createRuntimeDynamicTool("sessions_yield"),
23452341]);
2346-const harness = createStartedThreadHarness();
2347-const params = createParams(
2348-path.join(tempDir, "session.jsonl"),
2349-path.join(tempDir, "workspace"),
2350-);
2342+const workspaceDir = path.join(tempDir, "workspace");
2343+const params = createParams(path.join(tempDir, "session.jsonl"), workspaceDir);
23512344params.disableTools = false;
23522345params.runtimePlan = createCodexRuntimePlanFixture();
23532346params.sourceReplyDeliveryMode = "message_tool_only";
2347+const dynamicTools = await buildDynamicToolsForTest(params, workspaceDir, {
2348+forceHeartbeatTool: true,
2349+ignoreRuntimePlan: true,
2350+});
2351+const toolBridge = createCodexDynamicToolBridge({
2352+tools: dynamicTools,
2353+signal: new AbortController().signal,
2354+directToolNames: ["message"],
2355+});
2356+const request = vi.fn(async (method: string) => {
2357+if (method === "thread/start") {
2358+return threadStartResult();
2359+}
2360+throw new Error(`unexpected method: ${method}`);
2361+});
235423622355-const run = runCodexAppServerAttempt(params, {
2356-pluginConfig: { appServer: { mode: "yolo", codeModeOnly: true } },
2363+await startOrResumeThread({
2364+client: { request } as never,
2365+ params,
2366+cwd: workspaceDir,
2367+dynamicTools: toolBridge.specs,
2368+appServer: { ...createThreadLifecycleAppServerOptions(), codeModeOnly: true },
2369+nativeCodeModeEnabled: true,
2370+nativeCodeModeOnlyEnabled: true,
2371+userMcpServersEnabled: true,
2372+environmentSelection: [],
23572373});
2358-await harness.waitForMethod("turn/start", 120_000);
2359-await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
2360-await run;
236123742362-const startRequest = harness.requests.find((entry) => entry.method === "thread/start");
2363-const dynamicTools =
2364-(startRequest?.params as { dynamicTools?: Array<Record<string, unknown>> } | undefined)
2375+const startRequest = request.mock.calls.find(([method]) => method === "thread/start");
2376+const startDynamicTools =
2377+(startRequest?.[1] as { dynamicTools?: Array<Record<string, unknown>> } | undefined)
23652378?.dynamicTools ?? [];
2366-const startConfig = (startRequest?.params as { config?: Record<string, unknown> } | undefined)
2379+const startConfig = (startRequest?.[1] as { config?: Record<string, unknown> } | undefined)
23672380?.config;
2368-const message = dynamicTools.find((tool) => tool.name === "message");
2369-const webSearch = dynamicTools.find((tool) => tool.name === "web_search");
2370-const heartbeat = dynamicTools.find((tool) => tool.name === "heartbeat_respond");
2371-const sessionsSpawn = dynamicTools.find((tool) => tool.name === "sessions_spawn");
2372-const sessionsYield = dynamicTools.find((tool) => tool.name === "sessions_yield");
2381+const message = startDynamicTools.find((tool) => tool.name === "message");
2382+const webSearch = startDynamicTools.find((tool) => tool.name === "web_search");
2383+const heartbeat = startDynamicTools.find((tool) => tool.name === "heartbeat_respond");
2384+const sessionsSpawn = startDynamicTools.find((tool) => tool.name === "sessions_spawn");
2385+const sessionsYield = startDynamicTools.find((tool) => tool.name === "sessions_yield");
2373238623742387expect(message).not.toHaveProperty("namespace");
23752388expect(message).not.toHaveProperty("deferLoading");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。