codex-cli/src/utils/parsers.ts (82 lines of code) (raw):
import type {
ExecInput,
ExecOutputMetadata,
} from "./agent/sandbox/interface.js";
import type { ResponseFunctionToolCall } from "openai/resources/responses/responses.mjs";
import { log } from "node:console";
import { formatCommandForDisplay } from "src/format-command.js";
// The console utility import is intentionally explicit to avoid bundlers from
// including the entire `console` module when only the `log` function is
// required.
export function parseToolCallOutput(toolCallOutput: string): {
output: string;
metadata: ExecOutputMetadata;
} {
try {
const { output, metadata } = JSON.parse(toolCallOutput);
return {
output,
metadata,
};
} catch (err) {
return {
output: `Failed to parse JSON result`,
metadata: {
exit_code: 1,
duration_seconds: 0,
},
};
}
}
export type CommandReviewDetails = {
cmd: Array<string>;
cmdReadableText: string;
};
/**
* Tries to parse a tool call and, if successful, returns an object that has
* both:
* - an array of strings to use with `ExecInput` and `canAutoApprove()`
* - a human-readable string to display to the user
*/
export function parseToolCall(
toolCall: ResponseFunctionToolCall,
): CommandReviewDetails | undefined {
const toolCallArgs = parseToolCallArguments(toolCall.arguments);
if (toolCallArgs == null) {
return undefined;
}
const { cmd } = toolCallArgs;
const cmdReadableText = formatCommandForDisplay(cmd);
return {
cmd,
cmdReadableText,
};
}
/**
* If toolCallArguments is a string of JSON that can be parsed into an object
* with a "cmd" or "command" property that is an `Array<string>`, then returns
* that array. Otherwise, returns undefined.
*/
export function parseToolCallArguments(
toolCallArguments: string,
): ExecInput | undefined {
let json: unknown;
try {
json = JSON.parse(toolCallArguments);
} catch (err) {
log(`Failed to parse toolCall.arguments: ${toolCallArguments}`);
return undefined;
}
if (typeof json !== "object" || json == null) {
return undefined;
}
const { cmd, command } = json as Record<string, unknown>;
// The OpenAI model sometimes produces a single string instead of an array.
// Accept both shapes:
const commandArray =
toStringArray(cmd) ??
toStringArray(command) ??
(typeof cmd === "string" ? [cmd] : undefined) ??
(typeof command === "string" ? [command] : undefined);
if (commandArray == null) {
return undefined;
}
// @ts-expect-error timeout and workdir may not exist on json.
const { timeout, workdir } = json;
return {
cmd: commandArray,
workdir: typeof workdir === "string" ? workdir : undefined,
timeoutInMillis: typeof timeout === "number" ? timeout : undefined,
};
}
function toStringArray(obj: unknown): Array<string> | undefined {
if (Array.isArray(obj) && obj.every((item) => typeof item === "string")) {
const arrayOfStrings: Array<string> = obj;
return arrayOfStrings;
} else {
return undefined;
}
}