Normalize Codex dynamic tool transcript shape (#80155)

* Normalize Codex dynamic tool transcript shape

* test: align codex transcript aliases
This commit is contained in:
scoootscooob
2026-05-10 04:21:58 -04:00
committed by GitHub
parent 1f30ea39b5
commit f9d4e0c853
3 changed files with 97 additions and 1 deletions

View File

@@ -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");
});

View File

@@ -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,

View File

@@ -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);