Fix Codex SDK shell command hydration status

This commit is contained in:
Mohamed Boudra
2025-12-24 20:31:48 +07:00
parent 914ee6b390
commit c8f542fbe5
2 changed files with 86 additions and 46 deletions

View File

@@ -1591,17 +1591,9 @@ function handleRolloutFunctionCall(
}
if (SHELL_FUNCTION_NAMES.has(name)) {
const args = parseJsonOrObject<Record<string, unknown>>(payload.arguments);
const command = formatCommand(args);
const cwd =
args &&
typeof args === "object" &&
typeof (args as { workdir?: unknown }).workdir === "string"
? ((args as { workdir?: unknown }).workdir as string)
: undefined;
const { command, cwd } = buildCommandContext(payload.arguments);
if (typeof payload.call_id === "string") {
const commandValue = command ?? "Command";
commandCalls.set(payload.call_id, { command: commandValue, cwd });
commandCalls.set(payload.call_id, { command, cwd });
events.push({
type: "timeline",
provider: "codex",
@@ -1612,7 +1604,7 @@ function handleRolloutFunctionCall(
callId: payload.call_id,
displayName: buildCommandDisplayName(command),
kind: "execute",
input: { command: commandValue, cwd },
input: { command, cwd },
}),
});
}
@@ -1659,40 +1651,7 @@ function finalizeRolloutFunctionCall(
if (!command) {
return;
}
const result = parseJsonOrObject<CommandExecutionResult>(payload.output);
const parsedOutput =
typeof payload.output === "string"
? parseCommandOutputText(payload.output)
: null;
const exitCode = result?.metadata?.exit_code ?? parsedOutput?.exitCode;
const status =
exitCode === undefined || exitCode === 0 ? "completed" : "failed";
const stdout =
result?.stdout ??
(typeof result?.output === "string" ? result.output : undefined) ??
parsedOutput?.stdout;
const metadata =
result?.metadata ??
(typeof exitCode === "number" ? { exit_code: exitCode } : undefined);
// Build structured command output
let output: unknown;
if (stdout !== undefined) {
output = {
type: "command" as const,
command: command.command,
output: stdout,
exitCode,
cwd: command.cwd,
metadata,
};
} else if (result && typeof result === "object") {
output = { ...result, metadata: metadata ?? result.metadata };
} else if (metadata) {
output = { metadata };
} else {
output = result;
}
const { status, output } = buildCommandOutput(command, payload.output);
events.push({
type: "timeline",
@@ -1748,6 +1707,27 @@ function handleRolloutCustomToolCall(
return;
}
if (payload?.name && SHELL_FUNCTION_NAMES.has(payload.name)) {
const commandContext = buildCommandContext(payload.input);
const { status, output } = buildCommandOutput(commandContext, payload.output);
events.push({
type: "timeline",
provider: "codex",
item: createToolCallTimelineItem({
server: "command",
tool: "shell",
status: payload.status ?? status,
callId:
typeof payload.call_id === "string" ? payload.call_id : undefined,
displayName: buildCommandDisplayName(commandContext.command),
kind: "execute",
input: { command: commandContext.command, cwd: commandContext.cwd },
output,
}),
});
return;
}
if (typeof payload?.name === "string") {
events.push({
type: "timeline",
@@ -1810,6 +1790,7 @@ type RolloutCustomToolCallPayload = {
type: "custom_tool_call";
name?: string;
status?: string;
call_id?: string;
input?: string | Record<string, unknown>;
output?: unknown;
};
@@ -2158,6 +2139,64 @@ function parseCommandOutputText(
};
}
function buildCommandContext(input: unknown): { command: string; cwd?: string } {
const args = parseJsonOrObject<Record<string, unknown>>(input);
const commandFromArgs = formatCommand(args);
let command = commandFromArgs;
if (!command && typeof input === "string") {
const trimmed = input.trim();
if (trimmed && !trimmed.startsWith("{") && !trimmed.startsWith("[")) {
command = trimmed;
}
}
const cwd =
args &&
typeof args === "object" &&
typeof (args as { workdir?: unknown }).workdir === "string"
? ((args as { workdir?: unknown }).workdir as string)
: undefined;
return { command: command ?? "Command", cwd };
}
function buildCommandOutput(
command: { command: string; cwd?: string },
rawOutput: unknown
): { status: "completed" | "failed"; output: unknown } {
const result = parseJsonOrObject<CommandExecutionResult>(rawOutput);
const parsedOutput =
typeof rawOutput === "string" ? parseCommandOutputText(rawOutput) : null;
const exitCode = result?.metadata?.exit_code ?? parsedOutput?.exitCode;
const status =
exitCode === undefined || exitCode === 0 ? "completed" : "failed";
const stdout =
result?.stdout ??
(typeof result?.output === "string" ? result.output : undefined) ??
parsedOutput?.stdout;
const metadata =
result?.metadata ??
(typeof exitCode === "number" ? { exit_code: exitCode } : undefined);
let output: unknown;
if (stdout !== undefined) {
output = {
type: "command" as const,
command: command.command,
output: stdout,
exitCode,
cwd: command.cwd,
metadata,
};
} else if (result && typeof result === "object") {
output = { ...result, metadata: metadata ?? result.metadata };
} else if (metadata) {
output = { metadata };
} else {
output = result;
}
return { status, output };
}
function formatCommand(args: unknown): string | null {
if (!args || typeof args !== "object") {
return null;

View File

@@ -259,7 +259,8 @@ Build a new Codex MCP provider sidebyside with the existing Codex SDK prov
- If failures: add follow-up fix tasks immediately after this item.
- **Done (2025-12-24 20:25)**: Ran `npm run test --workspace=@paseo/server`; failures in Codex SDK persisted shell_command hydration and multiple Codex MCP mapping/persistence/permission checks; `agent-mcp.e2e.test.ts` hung and was interrupted.
- [ ] **Fix**: Codex SDK persisted shell_command hydration still missing completed status.
- [x] **Fix**: Codex SDK persisted shell_command hydration still missing completed status.
- **Done (2025-12-24 20:31)**: Mapped shell_command custom_tool_call entries to command tool calls and normalized output/status during rollout hydration.
- [ ] **Fix**: Codex MCP command output should include exit codes for command tool calls (missing in timeline mapping).