Skip to content
Open
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
140 changes: 111 additions & 29 deletions assets/inject/renderer-inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@
const codexThreadServiceTierMaxEntries = 120;
const codexThreadServiceTierDraftBindWindowMs = 60 * 1000;
const codexServiceTierRequestOverrideVersion = "3";
const codexAppServerModelRequestPatchVersion = "1";
const codexAppServerModelRequestPatchVersion = "20260712-app-server-fallback-v1";
const codexPluginMarketplaceUnlockVersion = "12";
const codexPluginAutoExpandVersion = "1";
const codexPluginAutoExpandMaxClicks = 80;
Expand Down Expand Up @@ -1405,25 +1405,40 @@
const codexThreadServiceTierModes = new Set(["inherit", "standard", "fast"]);
const codexServiceTierControlModes = new Set(["inherit", "global-standard", "global-fast", "custom"]);

function codexAppAssetUrl(namePart) {
const urls = [
function uniqueCodexAppAssetUrls(urls) {
return Array.from(new Set((urls || []).filter((url) => typeof url === "string" && url.includes("/assets/") && url.split("?")[0].endsWith(".js"))));
}

function codexAppAssetCandidateUrls() {
return uniqueCodexAppAssetUrls([
...Array.from(document.scripts || []).map((script) => script.src),
...Array.from(document.querySelectorAll("link[href]") || []).map((link) => link.href),
...performance.getEntriesByType("resource").map((entry) => entry.name),
].filter(Boolean);
return urls.find((url) => url.includes("/assets/") && url.includes(namePart) && url.split("?")[0].endsWith(".js")) || "";
]);
}

function codexAppAssetUrl(namePart) {
if (!namePart) return "";
return codexAppAssetCandidateUrls().find((url) => url.includes(namePart)) || "";
}

async function codexAppAssetUrlFromScriptText(namePart) {
const scripts = Array.from(document.scripts || []).map((script) => script.src).filter(Boolean);
if (!namePart) return "";
const scripts = codexAppAssetCandidateUrls();
const escaped = String(namePart).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const patterns = [
new RegExp(`["'](\\.?/assets/${escaped}[^"']+\\.js)["']`),
new RegExp(`["']([^"']*/assets/${escaped}[^"']+\\.js)["']`),
];
for (const src of scripts) {
if (!src.includes("/assets/") || !src.split("?")[0].endsWith(".js")) continue;
try {
const text = await fetch(src).then((response) => response.ok ? response.text() : "");
const escaped = namePart.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const match = text.match(new RegExp(`["'](\\./assets/${escaped}[^"']+\\.js)["']`));
if (!match) continue;
return new URL(match[1], src).href;
if (!text) continue;
for (const pattern of patterns) {
const match = text.match(pattern);
if (!match) continue;
return new URL(match[1], src).href;
}
} catch {
}
}
Expand All @@ -1445,6 +1460,84 @@
return await codexServiceTierModulePromises.get(namePart);
}

function appServerFallbackAssetUrls() {
const urls = codexAppAssetCandidateUrls();
const preferred = urls.filter((url) => {
const name = (url.split("/").pop() || "").toLowerCase();
return /app-server-manager-signals|app-initial|app-main|page-|chatg|signals|server-manager|gwqc41kz|c1urrgy0|hsvsqcnf/.test(name);
});
// Prefer explicit legacy name hits, then large initial bundles.
preferred.sort((left, right) => {
const score = (url) => {
const name = (url.split("/").pop() || "").toLowerCase();
if (name.includes("app-server-manager-signals")) return 0;
if (name.includes("gwqc41kz") || name.includes("c1urrgy0") || name.includes("hsvsqcnf")) return 1;
if (name.includes("app-initial") && name.includes("app-main")) return 2;
if (name.includes("app-main")) return 3;
return 4;
};
return score(left) - score(right) || right.length - left.length;
});
return preferred.slice(0, 16);
}

function collectAppServerRequestCandidatesFromModule(module) {
const candidates = [];
const seen = new Set();
const push = (value) => {
if (!value || typeof value !== "object" || seen.has(value)) return;
seen.add(value);
candidates.push(value);
};
for (const value of Object.values(module || {})) {
push(value);
if (!value || typeof value !== "object") continue;
if (typeof value.get === "function") {
try { push(value.get()); } catch {}
try { push(value.get("local")); } catch {}
}
try {
for (const nested of Object.values(value).slice(0, 100)) push(nested);
} catch {}
}
return candidates;
}

async function loadAppServerRequestModules() {
const modules = [];
const seenUrls = new Set();
const pushModule = (module) => {
if (module && typeof module === "object") modules.push(module);
};
try {
pushModule(await loadCodexAppModule("app-server-manager-signals-"));
} catch {
}
for (const url of appServerFallbackAssetUrls()) {
if (seenUrls.has(url)) continue;
seenUrls.add(url);
try {
pushModule(await import(url));
} catch {
}
}
return modules;
}

async function loadAppServerRequestCandidates() {
const modules = await loadAppServerRequestModules();
const candidates = [];
const seen = new Set();
for (const module of modules) {
for (const candidate of collectAppServerRequestCandidatesFromModule(module)) {
if (seen.has(candidate)) continue;
seen.add(candidate);
candidates.push(candidate);
}
}
return { modules, candidates };
}

async function codexSettingStorageModule() {
const module = await loadCodexAppModule("setting-storage-");
if (typeof module.n !== "function" || typeof module.s !== "function") {
Expand Down Expand Up @@ -3505,27 +3598,21 @@
if (!codexPlusSettings().pluginMarketplaceUnlock) return;
const patch = async () => {
try {
const module = await loadCodexAppModule("app-server-manager-signals-");
const candidates = Object.values(module).filter((value) => value && typeof value === "object");
const { modules, candidates } = await loadAppServerRequestCandidates();
let patchedCount = 0;
for (const candidate of candidates) {
if (patchPluginMarketplaceRequestClient(candidate)) patchedCount += 1;
if (typeof candidate.sendRequest !== "function" && typeof candidate.get === "function") {
try {
if (patchPluginMarketplaceRequestClient(candidate.get())) patchedCount += 1;
} catch {
}
}
}
if (patchedCount > 0) {
window.__codexPluginMarketplaceUnlockInstalled = codexPluginMarketplaceUnlockVersion;
sendCodexPlusDiagnostic("plugin_marketplace_request_patch_installed", {
moduleCount: modules.length,
candidateCount: candidates.length,
patchedCount,
});
} else {
sendCodexPlusDiagnostic("plugin_marketplace_request_patch_not_found", {
exportCount: Object.keys(module || {}).length,
moduleCount: modules.length,
candidateCount: candidates.length,
});
}
Expand Down Expand Up @@ -5032,28 +5119,23 @@
if (appServerModelRequestPatchDisabled) return;
const patch = async () => {
try {
const module = await loadCodexAppModule("app-server-manager-signals-");
const candidates = Object.values(module).filter((value) => value && typeof value === "object");
const { modules, candidates } = await loadAppServerRequestCandidates();
let patchedCount = 0;
for (const candidate of candidates) {
if (patchAppServerModelRequestClient(candidate)) patchedCount += 1;
if (typeof candidate.sendRequest !== "function" && typeof candidate.get === "function") {
try {
if (patchAppServerModelRequestClient(candidate.get())) patchedCount += 1;
} catch {
}
}
}
if (patchedCount > 0) {
appServerModelRequestPatchMissCount = 0;
window.__codexPlusAppServerModelRequestPatchInstalled = codexAppServerModelRequestPatchVersion;
sendCodexPlusDiagnostic("model_app_server_request_patch_installed", {
moduleCount: modules.length,
candidateCount: candidates.length,
patchedCount,
discovery: modules.length > 1 || !codexAppAssetUrl("app-server-manager-signals-") ? "fallback" : "legacy",
});
} else {
noteAppServerModelRequestPatchMiss("model_app_server_request_patch_not_found", {
exportCount: Object.keys(module || {}).length,
moduleCount: modules.length,
candidateCount: candidates.length,
});
}
Expand Down
19 changes: 19 additions & 0 deletions crates/codex-plus-core/tests/cdp_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,11 @@ fn injection_script_unlocks_custom_model_catalog() {
assert!(script.contains("patchStatsigModelDynamicConfig"));
assert!(script.contains("patchModelJsonResponse"));
assert!(script.contains("installAppServerModelRequestPatch"));
assert!(script.contains("loadAppServerRequestCandidates"));
assert!(script.contains("appServerFallbackAssetUrls"));
assert!(script.contains("collectAppServerRequestCandidatesFromModule"));
assert!(script.contains("20260712-app-server-fallback-v1"));

assert!(script.contains("list-models-for-host"));
assert!(script.contains("appServerModelRequestMethod"));
assert!(script.contains("send-cli-request-for-host"));
Expand Down Expand Up @@ -682,6 +687,20 @@ fn injection_script_discovers_vscode_api_asset_without_hardcoded_hash() {
assert!(!script.contains("import(\"./assets/vscode-api-"));
}


#[test]
fn injection_script_discovers_app_server_request_clients_without_hardcoded_hash() {
let script = assets::injection_script(57321);

assert!(script.contains("loadAppServerRequestCandidates"));
assert!(script.contains("appServerFallbackAssetUrls"));
assert!(script.contains("loadCodexAppModule(\"app-server-manager-signals-\""));
assert!(script.contains("candidateCount: candidates.length"));
assert!(script.contains("discovery:"));
// Keep legacy lookup as first attempt, but never hardcode the old hashed filename.
assert!(!script.contains("app-server-manager-signals-C1h8B-R-.js") || script.contains("refreshRecentConversationsForHost"));
}

#[test]
fn injection_script_clears_project_state_when_moving_to_projectless() {
let script = assets::injection_script(57321);
Expand Down