81 lines
2.7 KiB
JavaScript
81 lines
2.7 KiB
JavaScript
import { constants } from "node:fs";
|
|
import { access, readFile } from "node:fs/promises";
|
|
import { join, resolve } from "node:path";
|
|
|
|
export function codexCredentialFileCandidates(codexHome) {
|
|
return [join(codexHome, "auth.json"), join(codexHome, "config.toml")];
|
|
}
|
|
|
|
export async function findReadableCodexCredentialFiles(codexHome) {
|
|
const readable = [];
|
|
for (const candidate of codexCredentialFileCandidates(codexHome)) {
|
|
try {
|
|
await access(candidate, constants.R_OK);
|
|
readable.push(resolve(candidate));
|
|
} catch {
|
|
// A signed-in Codex installation may use either file. Probe both.
|
|
}
|
|
}
|
|
return readable;
|
|
}
|
|
|
|
export function createCodexDeniedHostPaths({
|
|
hostHome,
|
|
codexCredentialFiles,
|
|
unrelatedSecretPath,
|
|
}) {
|
|
return [...new Set([
|
|
resolve(hostHome),
|
|
...codexCredentialFiles.map((path) => resolve(path)),
|
|
resolve(unrelatedSecretPath),
|
|
])];
|
|
}
|
|
|
|
export function assertCodexTraceReadyForOutputs(trace) {
|
|
const decision = trace?.resultDecision?.status;
|
|
const disposition = trace?.result?.reportedWorkDisposition;
|
|
if (decision !== "accepted" || disposition !== "done") {
|
|
const summary = codexTraceSummary(trace);
|
|
throw new Error(
|
|
"Codex recorder requires an accepted `done` result before reading workspace outputs; " +
|
|
`received decision=${decision ?? "missing"}, disposition=${disposition ?? "missing"}` +
|
|
`${summary ? ` (${summary})` : ""}. Expected workspace outputs were not read.`,
|
|
);
|
|
}
|
|
|
|
const failedAssertions = Object.entries(trace?.assertions ?? {})
|
|
.filter(([, passed]) => passed !== true)
|
|
.map(([name]) => name);
|
|
if (failedAssertions.length > 0) {
|
|
throw new Error(
|
|
`Codex tracer reported failed assertions: ${failedAssertions.join(", ")}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
export async function readRequiredCodexOutput(path, displayName) {
|
|
try {
|
|
return await readFile(path, "utf8");
|
|
} catch (error) {
|
|
if (error && typeof error === "object" && error.code === "ENOENT") {
|
|
throw new Error(
|
|
`Codex recorder expected ${displayName} after an accepted \`done\` result, but the file was not created.`,
|
|
);
|
|
}
|
|
const detail = error instanceof Error ? error.message : String(error);
|
|
throw new Error(`Codex recorder could not read ${displayName}: ${detail}`);
|
|
}
|
|
}
|
|
|
|
function codexTraceSummary(trace) {
|
|
if (typeof trace?.result?.summary === "string" && trace.result.summary.trim().length > 0) {
|
|
return trace.result.summary.trim();
|
|
}
|
|
const issues = trace?.resultDecision?.issues;
|
|
if (!Array.isArray(issues)) return "";
|
|
return issues
|
|
.map((issue) => issue?.message)
|
|
.filter((message) => typeof message === "string" && message.trim().length > 0)
|
|
.join("; ");
|
|
}
|