Skip to content

Commit bb2bbc4

Browse files
committed
Normalize Codex dynamic tool transcript shape
1 parent 1a2664e commit bb2bbc4

3 files changed

Lines changed: 93 additions & 1 deletion

File tree

extensions/codex/src/app-server/event-projector.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,7 @@ describe("CodexAppServerEventProjector", () => {
795795
id: "call-browser-1",
796796
name: "browser",
797797
arguments: { action: "open", url: "http://127.0.0.1:3000" },
798+
input: { action: "open", url: "http://127.0.0.1:3000" },
798799
},
799800
],
800801
});
@@ -806,6 +807,9 @@ describe("CodexAppServerEventProjector", () => {
806807
content: [
807808
expect.objectContaining({
808809
type: "toolResult",
810+
id: "call-browser-1",
811+
name: "browser",
812+
toolName: "browser",
809813
toolCallId: "call-browser-1",
810814
content: "opened",
811815
}),

extensions/codex/src/app-server/event-projector.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1016,14 +1016,16 @@ export class CodexAppServerEventProjector {
10161016
}
10171017

10181018
private createToolCallMessage(params: ToolTranscriptCallInput): AgentMessage {
1019+
const args = normalizeToolTranscriptArguments(params.arguments);
10191020
return {
10201021
role: "assistant",
10211022
content: [
10221023
{
10231024
type: "toolCall",
10241025
id: params.id,
10251026
name: params.name,
1026-
arguments: normalizeToolTranscriptArguments(params.arguments),
1027+
arguments: args,
1028+
input: args,
10271029
},
10281030
],
10291031
api: this.params.model.api ?? "openai-codex-responses",
@@ -1045,6 +1047,9 @@ export class CodexAppServerEventProjector {
10451047
content: [
10461048
{
10471049
type: "toolResult",
1050+
id: params.id,
1051+
name: params.name,
1052+
toolName: params.name,
10481053
toolCallId: params.id,
10491054
toolUseId: params.id,
10501055
tool_use_id: params.id,

extensions/codex/src/app-server/run-attempt.test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,89 @@ describe("runCodexAppServerAttempt", () => {
693693
expect(heartbeat?.deferLoading).toBe(true);
694694
});
695695

696+
it("keeps searchable Codex dynamic tools canonical in mirrored transcript snapshots", async () => {
697+
__testing.setOpenClawCodingToolsFactoryForTests(() => [
698+
createRuntimeDynamicTool("wiki_status"),
699+
]);
700+
const harness = createStartedThreadHarness();
701+
const params = createParams(
702+
path.join(tempDir, "session.jsonl"),
703+
path.join(tempDir, "workspace"),
704+
);
705+
params.disableTools = false;
706+
params.runtimePlan = createCodexRuntimePlanFixture();
707+
params.toolsAllow = ["wiki_status"];
708+
709+
const run = runCodexAppServerAttempt(params, {
710+
pluginConfig: {
711+
codexDynamicToolsLoading: "searchable",
712+
appServer: { mode: "yolo" },
713+
},
714+
});
715+
await harness.waitForMethod("turn/start", 120_000);
716+
717+
const toolResult = (await harness.handleServerRequest({
718+
id: "request-tool-wiki-status",
719+
method: "item/tool/call",
720+
params: {
721+
threadId: "thread-1",
722+
turnId: "turn-1",
723+
callId: "call-wiki-status-1",
724+
namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
725+
tool: "wiki_status",
726+
arguments: { topic: "README.md" },
727+
},
728+
})) as {
729+
contentItems?: Array<{ text?: string; type?: string }>;
730+
success?: boolean;
731+
};
732+
expect(toolResult).toEqual({
733+
success: true,
734+
contentItems: [{ type: "inputText", text: "wiki_status done" }],
735+
});
736+
737+
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
738+
const result = await run;
739+
740+
expect(result.messagesSnapshot.map((message) => message.role)).toEqual([
741+
"user",
742+
"assistant",
743+
"toolResult",
744+
]);
745+
expect(result.messagesSnapshot[1]).toMatchObject({
746+
role: "assistant",
747+
content: [
748+
{
749+
type: "toolCall",
750+
id: "call-wiki-status-1",
751+
name: "wiki_status",
752+
arguments: { topic: "README.md" },
753+
input: { topic: "README.md" },
754+
},
755+
],
756+
});
757+
expect(result.messagesSnapshot[2]).toMatchObject({
758+
role: "toolResult",
759+
toolCallId: "call-wiki-status-1",
760+
toolName: "wiki_status",
761+
isError: false,
762+
content: [
763+
expect.objectContaining({
764+
type: "toolResult",
765+
id: "call-wiki-status-1",
766+
name: "wiki_status",
767+
toolName: "wiki_status",
768+
toolCallId: "call-wiki-status-1",
769+
toolUseId: "call-wiki-status-1",
770+
tool_use_id: "call-wiki-status-1",
771+
content: "wiki_status done",
772+
}),
773+
],
774+
});
775+
expect(JSON.stringify(result.messagesSnapshot)).not.toContain("tool_search");
776+
expect(JSON.stringify(result.messagesSnapshot)).not.toContain("function_call_output");
777+
});
778+
696779
it("passes the live run session key to Codex dynamic tools when sandbox policy uses another key", () => {
697780
const workspaceDir = path.join(tempDir, "workspace");
698781
const params = createParams(path.join(tempDir, "session.jsonl"), workspaceDir);

0 commit comments

Comments
 (0)