From f9d4e0c853ff50b9d5ed31eb8fd4e124235b30a0 Mon Sep 17 00:00:00 2001 From: scoootscooob <167050519+scoootscooob@users.noreply.github.com> Date: Sun, 10 May 2026 04:21:58 -0400 Subject: [PATCH] Normalize Codex dynamic tool transcript shape (#80155) * Normalize Codex dynamic tool transcript shape * test: align codex transcript aliases --- .../src/app-server/event-projector.test.ts | 8 ++ .../codex/src/app-server/event-projector.ts | 7 +- .../codex/src/app-server/run-attempt.test.ts | 83 +++++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/extensions/codex/src/app-server/event-projector.test.ts b/extensions/codex/src/app-server/event-projector.test.ts index 66984e84e76..30807664e7c 100644 --- a/extensions/codex/src/app-server/event-projector.test.ts +++ b/extensions/codex/src/app-server/event-projector.test.ts @@ -858,6 +858,7 @@ describe("CodexAppServerEventProjector", () => { id: "cmd-1", name: "bash", arguments: { command: "pnpm test extensions/codex", cwd: "/workspace" }, + input: { command: "pnpm test extensions/codex", cwd: "/workspace" }, }); const toolResultMessage = requireRecord(result.messagesSnapshot[2], "tool result message"); expect(toolResultMessage.role).toBe("toolResult"); @@ -867,6 +868,9 @@ describe("CodexAppServerEventProjector", () => { const toolResultContent = requireArray(toolResultMessage.content, "tool result content"); const toolResultContentItem = requireRecord(toolResultContent[0], "tool result content item"); expect(toolResultContentItem.type).toBe("toolResult"); + expect(toolResultContentItem.id).toBe("cmd-1"); + expect(toolResultContentItem.name).toBe("bash"); + expect(toolResultContentItem.toolName).toBe("bash"); expect(toolResultContentItem.toolCallId).toBe("cmd-1"); expect(toolResultContentItem.content).toBe("ok"); }); @@ -902,6 +906,7 @@ describe("CodexAppServerEventProjector", () => { id: "call-browser-1", name: "browser", arguments: { action: "open", url: "http://127.0.0.1:3000" }, + input: { action: "open", url: "http://127.0.0.1:3000" }, }); const toolResultMessage = requireRecord(result.messagesSnapshot[2], "tool result message"); expect(toolResultMessage.role).toBe("toolResult"); @@ -913,6 +918,9 @@ describe("CodexAppServerEventProjector", () => { "tool result content item", ); expect(toolResultContent.type).toBe("toolResult"); + expect(toolResultContent.id).toBe("call-browser-1"); + expect(toolResultContent.name).toBe("browser"); + expect(toolResultContent.toolName).toBe("browser"); expect(toolResultContent.toolCallId).toBe("call-browser-1"); expect(toolResultContent.content).toBe("opened"); }); diff --git a/extensions/codex/src/app-server/event-projector.ts b/extensions/codex/src/app-server/event-projector.ts index 69610299502..a81a5dace31 100644 --- a/extensions/codex/src/app-server/event-projector.ts +++ b/extensions/codex/src/app-server/event-projector.ts @@ -1041,6 +1041,7 @@ export class CodexAppServerEventProjector { } private createToolCallMessage(params: ToolTranscriptCallInput): AgentMessage { + const args = normalizeToolTranscriptArguments(params.arguments); return { role: "assistant", content: [ @@ -1048,7 +1049,8 @@ export class CodexAppServerEventProjector { type: "toolCall", id: params.id, name: params.name, - arguments: normalizeToolTranscriptArguments(params.arguments), + arguments: args, + input: args, }, ], api: this.params.model.api ?? "openai-codex-responses", @@ -1070,6 +1072,9 @@ export class CodexAppServerEventProjector { content: [ { type: "toolResult", + id: params.id, + name: params.name, + toolName: params.name, toolCallId: params.id, toolUseId: params.id, tool_use_id: params.id, diff --git a/extensions/codex/src/app-server/run-attempt.test.ts b/extensions/codex/src/app-server/run-attempt.test.ts index d2ad5341d1d..a1f85836d65 100644 --- a/extensions/codex/src/app-server/run-attempt.test.ts +++ b/extensions/codex/src/app-server/run-attempt.test.ts @@ -734,6 +734,89 @@ describe("runCodexAppServerAttempt", () => { expect(heartbeat?.deferLoading).toBe(true); }); + it("keeps searchable Codex dynamic tools canonical in mirrored transcript snapshots", async () => { + __testing.setOpenClawCodingToolsFactoryForTests(() => [ + createRuntimeDynamicTool("wiki_status"), + ]); + const harness = createStartedThreadHarness(); + const params = createParams( + path.join(tempDir, "session.jsonl"), + path.join(tempDir, "workspace"), + ); + params.disableTools = false; + params.runtimePlan = createCodexRuntimePlanFixture(); + params.toolsAllow = ["wiki_status"]; + + const run = runCodexAppServerAttempt(params, { + pluginConfig: { + codexDynamicToolsLoading: "searchable", + appServer: { mode: "yolo" }, + }, + }); + await harness.waitForMethod("turn/start", 120_000); + + const toolResult = (await harness.handleServerRequest({ + id: "request-tool-wiki-status", + method: "item/tool/call", + params: { + threadId: "thread-1", + turnId: "turn-1", + callId: "call-wiki-status-1", + namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE, + tool: "wiki_status", + arguments: { topic: "README.md" }, + }, + })) as { + contentItems?: Array<{ text?: string; type?: string }>; + success?: boolean; + }; + expect(toolResult).toEqual({ + success: true, + contentItems: [{ type: "inputText", text: "wiki_status done" }], + }); + + await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" }); + const result = await run; + + expect(result.messagesSnapshot.map((message) => message.role)).toEqual([ + "user", + "assistant", + "toolResult", + ]); + expect(result.messagesSnapshot[1]).toMatchObject({ + role: "assistant", + content: [ + { + type: "toolCall", + id: "call-wiki-status-1", + name: "wiki_status", + arguments: { topic: "README.md" }, + input: { topic: "README.md" }, + }, + ], + }); + expect(result.messagesSnapshot[2]).toMatchObject({ + role: "toolResult", + toolCallId: "call-wiki-status-1", + toolName: "wiki_status", + isError: false, + content: [ + expect.objectContaining({ + type: "toolResult", + id: "call-wiki-status-1", + name: "wiki_status", + toolName: "wiki_status", + toolCallId: "call-wiki-status-1", + toolUseId: "call-wiki-status-1", + tool_use_id: "call-wiki-status-1", + content: "wiki_status done", + }), + ], + }); + expect(JSON.stringify(result.messagesSnapshot)).not.toContain("tool_search"); + expect(JSON.stringify(result.messagesSnapshot)).not.toContain("function_call_output"); + }); + it("passes the live run session key to Codex dynamic tools when sandbox policy uses another key", () => { const workspaceDir = path.join(tempDir, "workspace"); const params = createParams(path.join(tempDir, "session.jsonl"), workspaceDir);