1const REPLAY_WORKSPACE_PLACEHOLDER = "<workspace>";
2const REPLAY_HOME = "/home/replay-user";
3const REPLAY_CHECKOUT = "/home/replay-user/t3code";
4const REPLAY_HOSTNAME = "replay-host";
5/** Account- and install-scoped values Codex reports; the adapter reads none of them. */
6const REPLAY_VALUES_BY_KEY: Readonly<Record<string, string>> = {
7 installationId: "00000000-0000-4000-8000-000000000000",
8 planType: "unknown",
9};
10
11interface CodexReplayRecordingMachine {
12 readonly home: string;
13 readonly hostname: string;
14 /** Root of the checkout the recorder runs from; it names the local worktree layout. */
15 readonly checkout: string;
16}
17
18function withWorkspacePlaceholder(value: unknown, workspace: string): unknown {
19 if (value === workspace) {
20 return REPLAY_WORKSPACE_PLACEHOLDER;
21 }
22 if (Array.isArray(value)) {
23 return value.map((entry) => withWorkspacePlaceholder(entry, workspace));
24 }
25 if (typeof value !== "object" || value === null) {
26 return value;
27 }
28 return Object.fromEntries(
29 Object.entries(value).map(([key, entry]) => [key, withWorkspacePlaceholder(entry, workspace)]),
30 );
31}
32
33function withoutMachineIdentity(
34 value: unknown,
35 machine: CodexReplayRecordingMachine,
36 key?: string,
37): unknown {
38 if (typeof value === "string") {
39 const replacement = key === undefined ? undefined : REPLAY_VALUES_BY_KEY[key];
40 if (replacement !== undefined) return replacement;
41 return value === machine.hostname
42 ? REPLAY_HOSTNAME
43 : value.replaceAll(machine.checkout, REPLAY_CHECKOUT).replaceAll(machine.home, REPLAY_HOME);
44 }
45 if (Array.isArray(value)) {
46 return value.map((entry) => withoutMachineIdentity(entry, machine));
47 }
48 if (typeof value !== "object" || value === null) {
49 return value;
50 }
51 return Object.fromEntries(
52 Object.entries(value).map(([entryKey, entry]) => [
53 entryKey,
54 withoutMachineIdentity(entry, machine, entryKey),
55 ]),
56 );
57}
58
59/**
60 * Codex supports multiple provider threads in one app-server session, so native
61 * fork recording keeps the request ids emitted by that single client. Outbound
62 * frames name the recording cwd `<workspace>`, which replay swaps for its own
63 * checkpoint workspace. With `machine`, every frame also swaps the checkout
64 * path, home directory, hostname, installation id and plan type for neutral
65 * values so fixtures carry no local identity; the adapter reads none of them.
66 */
67export function codexReplayRecordingOutputRecords(
68 records: ReadonlyArray<Record<string, unknown>>,
69 options: {
70 readonly workspace: string;
71 readonly machine?: CodexReplayRecordingMachine;
72 },
73): ReadonlyArray<Record<string, unknown>> {
74 return records.map((record) => {
75 const named =
76 record.type === "expect_outbound"
77 ? { ...record, frame: withWorkspacePlaceholder(record.frame, options.workspace) }
78 : record;
79 return options.machine === undefined || !("frame" in named)
80 ? named
81 : { ...named, frame: withoutMachineIdentity(named.frame, options.machine) };
82 });
83}