Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass `

If the script reports that a chunk or function cannot be found, the Codex version has probably changed. Update the search markers in `tools/build-inplace-asar.mjs` and rebuild.

The builder discovers hashed model-selector and React DOM asset filenames automatically. Minified component names can still change between Codex releases; version-specific mappings live in `tools/selector-compatibility.mjs` and are covered by `npm run check`.

## Advantages

| Area | Benefit |
Expand Down Expand Up @@ -174,6 +176,10 @@ Quit Codex from the Windows notification-area icon. Closing only the main window

Rebuild from the newest official archive. This project does not manufacture a model list: it reuses the catalog exposed by Codex.

### The application shows “An error occurred” after a Codex update

Run `npm run check`, extract `app.asar` from the current official installation, and rebuild. React error 130 usually means Codex renamed a minified component used by the selector patch. Update the matching profile in `tools/selector-compatibility.mjs`; do not map JSX targets to JSX-runtime or lazy-loader objects.

### I want to return to official Codex

Close the customized copy, then launch Codex from the Start menu. No uninstall or file restoration is required.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
"@electron/asar": "4.2.0"
},
"scripts": {
"check": "node --check tools/build-inplace-asar.mjs"
"check": "node --check tools/build-inplace-asar.mjs && node --test tools/*.test.mjs"
}
}
68 changes: 46 additions & 22 deletions tools/build-inplace-asar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import fs from "node:fs";
import path from "node:path";
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
import {
adaptCompactSelector,
selectSelectorCompatibilityProfile,
} from "./selector-compatibility.mjs";
const require = createRequire(import.meta.url);
const asarLib = path.dirname(require.resolve("@electron/asar"));
const { readArchiveHeaderSync } = await import(`${pathToFileURL(asarLib).href}/disk.js`);
Expand All @@ -13,20 +17,30 @@ const destinationArchive = path.resolve(
process.argv[3] ?? "outputs/Codex-Native-Selector/app/resources/app.asar",
);
const extractedRoot = path.resolve(process.argv[5] ?? "work/asar-extracted");
const chunkRelativePath =
"webview/assets/model-and-reasoning-dropdown-DFupBzq6.js";
const assetsRoot = path.join(extractedRoot, "webview/assets");
const findSingleAsset = (pattern, description) => {
const matches = fs.readdirSync(assetsRoot).filter((name) => pattern.test(name));
if (matches.length !== 1) {
throw new Error(`Expected one ${description} asset, found ${matches.length}.`);
}
return matches[0];
};
const chunkFileName = findSingleAsset(
/^model-and-reasoning-dropdown-.*\.js$/u,
"model selector",
);
const reactDomFileName = findSingleAsset(/^react-dom-.*\.js$/u, "React DOM");
const chunkRelativePath = `webview/assets/${chunkFileName}`;
const chunkSourcePath = path.join(extractedRoot, chunkRelativePath);

let source = fs.readFileSync(chunkSourcePath, "utf8");
source = `import{t as Cp}from"./react-dom-CTTwO1mS.js";${source}`;
source = `import{t as Cp}from"./${reactDomFileName}";${source}`;

const catalogBefore =
"function K(e){let t=q(ce,e);if(t.length>=4)return t;let n=q(le,e);return n.length>=4?n:[]}";
const catalogAfter = "function K(e){return oe(e)}";
if (!source.includes(catalogBefore)) {
throw new Error("The original compact catalog function was not found.");
}
source = source.replace(catalogBefore, catalogAfter);
const compatibilityProfile = selectSelectorCompatibilityProfile(source);
source = source.replace(
compatibilityProfile.catalogBefore,
compatibilityProfile.catalogAfter,
);

const fieldsBefore = "supportedReasoningEfforts:n})=>{let r=";
const fieldsAfter =
Expand All @@ -39,8 +53,12 @@ if (!source.includes(fieldsBefore) || !source.includes(valueBefore)) {
}
source = source.replace(fieldsBefore, fieldsAfter).replace(valueBefore, valueAfter);

const componentStart = source.indexOf("function Ve(e){");
const componentEnd = source.indexOf("function He(e){", componentStart);
const { componentName, nextComponentName } = compatibilityProfile;
const componentStart = source.indexOf(`function ${componentName}(e){`);
const componentEnd = source.indexOf(
`function ${nextComponentName}(e){`,
componentStart,
);
if (componentStart < 0 || componentEnd < 0) {
throw new Error("The original compact selector component was not found.");
}
Expand All @@ -53,22 +71,29 @@ function Ck(e,t){let n=`gpt-${t}`;return e===n?`full`:e.startsWith(`${n}-`)?e.sl
function Ve(e){let{active:t,fastModeEnabled:n,onDragToMax:r,onSelectPower:i,powerSelections:a,selectedPowerSelection:o,shouldReduceMotion:s,transitionsReady:l}=e;if(o==null)return(0,Z.jsx)(`div`,{className:X.SimpleView});Cs();let u=Cf(o.model),d=[...new Set(a.filter(e=>Cf(e.model)===u).map(e=>e.model))],f=a.filter(e=>e.model===o.model),p=f.map(e=>({id:e.id,isMax:e.reasoningEffort===`ultra`,label:e.modelLabel})),m=e=>{let t=a.find(t=>t.id===e.id);t&&i(t)},h=e=>{let t=a.filter(t=>t.model===e),n=t.find(e=>e.reasoningEffort===o.reasoningEffort)??t.find(e=>e.reasoningEffort===e.defaultReasoningEffort)??t[0];n&&i(n)},g=d.length>1?(0,Z.jsx)(`div`,{className:`codex-tabs`,role:`tablist`,children:d.map(e=>(0,Z.jsx)(`button`,{type:`button`,role:`tab`,"aria-selected":e===o.model,"data-s":e===o.model,onPointerDown:e=>e.preventDefault(),onClick:t=>{t.preventDefault(),t.stopPropagation(),h(e)},children:Cv(e,u)},e))}):null;return(0,Z.jsxs)(`div`,{className:`${X.SimpleView} codex-native`,"data-v":Ck(o.model,u),children:[g,(0,Z.jsx)(ye,{active:t,fastModeEnabled:n,keyboardControlFocused:!1,onDragToMax:r,onSelectOption:m,options:p,selectedOptionId:o.id,shouldReduceMotion:s,transitionsReady:l})]})}
*/}

const compactSelector = fs
.readFileSync(path.resolve(process.argv[4] ?? "tools/selector-v2.js.txt"), "utf8")
.replace(/^\uFEFF/u, "");
const compactSelector = adaptCompactSelector(
fs
.readFileSync(path.resolve(process.argv[4] ?? "tools/selector-v2.js.txt"), "utf8")
.replace(/^\uFEFF/u, ""),
compatibilityProfile,
);
source =
source.slice(0, componentStart) +
compactSelector +
source.slice(componentEnd);

const controlsStart = source.indexOf("function rt(e){");
const controlsEnd = source.indexOf("function it(e){", controlsStart);
const { controlsName, nextControlsName } = compatibilityProfile;
const controlsStart = source.indexOf(`function ${controlsName}(e){`);
const controlsEnd = source.indexOf(
`function ${nextControlsName}(e){`,
controlsStart,
);
if (controlsStart < 0 || controlsEnd < 0) {
throw new Error("The original compact selector controls were not found.");
}
source =
source.slice(0, controlsStart) +
"function rt(e){let{ref:t}=e;return(0,Q.jsx)(`div`,{className:`codex-controls-placeholder`,ref:t})}" +
`function ${controlsName}(e){let{ref:t}=e;return(0,Q.jsx)(\`div\`,{className:\`codex-controls-placeholder\`,ref:t})}` +
source.slice(controlsEnd);

const sliderPropsBefore =
Expand All @@ -80,15 +105,14 @@ if (!source.includes(sliderPropsBefore)) {
}
source = source.replace(sliderPropsBefore, sliderPropsAfter);

const advancedStateBefore = "D=(0,ft.useRef)(null),O=i===`advanced`,k=";
const advancedStateAfter = "D=(0,ft.useRef)(null),O=!1,k=";
const { advancedStateBefore } = compatibilityProfile;
const menuViewBefore = '"data-view":i,style:N';
const menuViewAfter = '"data-view":`simple`,style:N';
if (!source.includes(advancedStateBefore) || !source.includes(menuViewBefore)) {
if (!source.includes(menuViewBefore)) {
throw new Error("The advanced-view state wiring was not found.");
}
source = source
.replace(advancedStateBefore, advancedStateAfter)
.replace(advancedStateBefore, advancedStateBefore.replace("i===`advanced`", "!1"))
.replace(menuViewBefore, menuViewAfter);

const originalChunk = fs.readFileSync(chunkSourcePath);
Expand Down
5 changes: 4 additions & 1 deletion tools/build-portable.ps1
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
param(
[Parameter(Mandatory = $true)]
[string]$InstallApp,
[string]$Destination = (Join-Path (Split-Path -Parent $PSScriptRoot) 'outputs\Codex-Native-Selector')
[string]$Destination
)

$ErrorActionPreference = 'Stop'
$workspace = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
if ([string]::IsNullOrWhiteSpace($Destination)) {
$Destination = Join-Path $workspace 'outputs\Codex-Native-Selector'
}
$destinationFull = [System.IO.Path]::GetFullPath($Destination)
$installFull = [System.IO.Path]::GetFullPath($InstallApp)
if (-not (Test-Path -LiteralPath $installFull)) { throw "Codex app directory not found: $installFull" }
Expand Down
98 changes: 98 additions & 0 deletions tools/selector-compatibility.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
export const selectorCompatibilityProfiles = [
{
name: "legacy",
catalogBefore:
"function K(e){let t=q(ce,e);if(t.length>=4)return t;let n=q(le,e);return n.length>=4?n:[]}",
catalogAfter: "function K(e){return oe(e)}",
componentName: "Ve",
nextComponentName: "He",
controlsName: "rt",
nextControlsName: "it",
advancedStateBefore: "D=(0,ft.useRef)(null),O=i===`advanced`,k=",
hooksName: "Ge",
fastPredicate: "it",
standardPredicate: "at",
fastIcon: "Qe",
standardIcon: "tt",
sliderComponent: "ye",
runtimeMarkers: [
"Ge=t(n(),1)",
"function it(e){let{iconKind:t}=e;return t===`fast`}",
"function at(e){let{value:t}=e;return t==null}",
"(0,Q.jsx)(Qe,",
"(0,Q.jsx)(tt,",
"(0,Z.jsx)(ye,{active:",
],
},
{
name: "codex-26.707",
catalogBefore:
"function ae(e,t=!1){let n=K(t?[...q,le]:q,e);if(n.length>=4)return n;let r=K(ue,e);return r.length>=4?r:[]}",
catalogAfter: "function ae(e,t=!1){return se(e)}",
componentName: "He",
nextComponentName: "Ue",
controlsName: "it",
nextControlsName: "at",
advancedStateBefore: "D=(0,pt.useRef)(null),O=i===`advanced`,k=",
hooksName: "Ke",
fastPredicate: "at",
standardPredicate: "ot",
fastIcon: "$e",
standardIcon: "nt",
sliderComponent: "be",
runtimeMarkers: [
"Ke=t(n(),1)",
"function at(e){let{iconKind:t}=e;return t===`fast`}",
"function ot(e){let{value:t}=e;return t==null}",
"(0,Q.jsx)($e,",
"(0,Q.jsx)(nt,",
"(0,Z.jsx)(be,{active:",
],
},
];

function sourceMarkers(profile) {
return [
profile.catalogBefore,
`function ${profile.componentName}(e){`,
`function ${profile.nextComponentName}(e){`,
`function ${profile.controlsName}(e){`,
`function ${profile.nextControlsName}(e){`,
profile.advancedStateBefore,
...profile.runtimeMarkers,
];
}

export function selectSelectorCompatibilityProfile(source) {
const matches = selectorCompatibilityProfiles.filter((profile) =>
sourceMarkers(profile).every((marker) => source.includes(marker)),
);
if (matches.length !== 1) {
throw new Error(
`Expected one selector compatibility profile, found ${matches.length}.`,
);
}
return matches[0];
}

export function adaptCompactSelector(template, profile) {
const replacements = [
["function Ve(e){", `function ${profile.componentName}(e){`],
["(0,Ge.", `(0,${profile.hooksName}.`],
["l?.find(at)", `l?.find(${profile.standardPredicate})`],
["l?.find(it)", `l?.find(${profile.fastPredicate})`],
["(0,Z.jsx)(Qe,", `(0,Z.jsx)(${profile.fastIcon},`],
["(0,Z.jsx)(tt,", `(0,Z.jsx)(${profile.standardIcon},`],
["(0,Z.jsx)(ye,", `(0,Z.jsx)(${profile.sliderComponent},`],
];

let selector = template;
for (const [before, after] of replacements) {
if (!selector.includes(before)) {
throw new Error(`Selector template marker not found: ${before}`);
}
selector = selector.replaceAll(before, after);
}

return selector;
}
87 changes: 87 additions & 0 deletions tools/selector-compatibility.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
adaptCompactSelector,
selectSelectorCompatibilityProfile,
selectorCompatibilityProfiles,
} from "./selector-compatibility.mjs";

const template = readFileSync(
new URL("./selector-v2.js.txt", import.meta.url),
"utf8",
).replace(/^\uFEFF/u, "");

function sourceFor(profile) {
return [
profile.catalogBefore,
`function ${profile.componentName}(e){}`,
`function ${profile.nextComponentName}(e){}`,
`function ${profile.controlsName}(e){}`,
`function ${profile.nextControlsName}(e){}`,
profile.advancedStateBefore,
...profile.runtimeMarkers,
].join(";");
}

test("selects legacy and current profiles only from their complete fingerprints", () => {
for (const profile of selectorCompatibilityProfiles) {
assert.equal(selectSelectorCompatibilityProfile(sourceFor(profile)), profile);
}
});

test("rejects unknown and ambiguous bundle fingerprints", () => {
assert.throws(
() => selectSelectorCompatibilityProfile("function Ve(e){}"),
/found 0/u,
);
assert.throws(
() =>
selectSelectorCompatibilityProfile(
selectorCompatibilityProfiles.map(sourceFor).join(";"),
),
/found 2/u,
);
});

test("rejects symbol-only drift in every runtime marker", () => {
for (const profile of selectorCompatibilityProfiles) {
for (const runtimeMarker of profile.runtimeMarkers) {
const source = sourceFor(profile).replace(runtimeMarker, "renamed-symbol");
assert.throws(
() => selectSelectorCompatibilityProfile(source),
/found 0/u,
`${profile.name} should reject a changed marker: ${runtimeMarker}`,
);
}
}
});

test("keeps the legacy selector symbols for the legacy profile", () => {
const selector = adaptCompactSelector(template, selectorCompatibilityProfiles[0]);

assert.match(selector, /function Ve\(e\)\{/u);
assert.match(selector, /\(0,Z\.jsx\)\(Qe,/u);
assert.match(selector, /\(0,Z\.jsx\)\(tt,/u);
assert.match(selector, /\(0,Z\.jsx\)\(ye,/u);
});

test("maps Codex 26.707 icons and slider to renderable component symbols", () => {
const selector = adaptCompactSelector(template, selectorCompatibilityProfiles[1]);

assert.match(selector, /function He\(e\)\{/u);
assert.match(selector, /\(0,Ke\.useState\)/u);
assert.match(selector, /l\?\.find\(at\)/u);
assert.match(selector, /l\?\.find\(ot\)/u);
assert.match(selector, /\(0,Z\.jsx\)\(\$e,/u);
assert.match(selector, /\(0,Z\.jsx\)\(nt,/u);
assert.match(selector, /\(0,Z\.jsx\)\(be,/u);
assert.doesNotMatch(selector, /\(0,Z\.jsx\)\((?:Qe|tt|ye),/u);
});

test("fails closed when the selector template no longer matches", () => {
assert.throws(
() => adaptCompactSelector("function changed(){}", selectorCompatibilityProfiles[1]),
/Selector template marker not found/u,
);
});