From f4c1c3d1f222ac29f4a567c926f2f8c8531d56bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B1=86=E5=8C=85?= Date: Thu, 18 Jun 2026 08:56:17 +0800 Subject: [PATCH 1/7] fix: enable native fast service tier for API-key auth --- apps/codex-plus-manager/src/App.tsx | 4 +- assets/inject/renderer-inject.js | 13 +- crates/codex-plus-core/src/launcher.rs | 83 ++++++- crates/codex-plus-core/src/lib.rs | 1 + .../src/service_tier_preload.rs | 235 ++++++++++++++++++ crates/codex-plus-core/src/settings.rs | 5 +- crates/codex-plus-core/tests/cdp_bridge.rs | 6 +- crates/codex-plus-core/tests/launcher.rs | 9 +- 8 files changed, 337 insertions(+), 19 deletions(-) create mode 100644 crates/codex-plus-core/src/service_tier_preload.rs diff --git a/apps/codex-plus-manager/src/App.tsx b/apps/codex-plus-manager/src/App.tsx index e6014c8f8..ab7ce048f 100644 --- a/apps/codex-plus-manager/src/App.tsx +++ b/apps/codex-plus-manager/src/App.tsx @@ -645,7 +645,7 @@ const defaultSettings: BackendSettings = { codexAppUpstreamWorktreeCreate: true, codexAppNativeMenuPlacement: true, codexAppNativeMenuLocalization: true, - codexAppServiceTierControls: false, + codexAppServiceTierControls: true, codexAppImageOverlayEnabled: false, codexAppImageOverlayPath: "", codexAppImageOverlayOpacity: 35, @@ -2733,7 +2733,7 @@ function EnhanceScreen({ setEnhanceFlag("codexAppForcePluginInstall", value)} /> setEnhanceFlag("codexAppPluginAutoExpand", value)} /> setEnhanceFlag("codexAppModelWhitelistUnlock", value)} /> - setEnhanceFlag("codexAppServiceTierControls", value)} /> + {}} /> setEnhanceFlag("codexAppSessionDelete", value)} /> setEnhanceFlag("codexAppMarkdownExport", value)} /> setEnhanceFlag("codexAppPasteFix", value)} /> diff --git a/assets/inject/renderer-inject.js b/assets/inject/renderer-inject.js index 67e1da895..3c178ab22 100644 --- a/assets/inject/renderer-inject.js +++ b/assets/inject/renderer-inject.js @@ -915,7 +915,7 @@ } function defaultCodexPlusSettings() { - return { pluginMarketplaceUnlock: true, forcePluginInstall: true, pluginAutoExpand: true, modelWhitelistUnlock: true, sessionDelete: true, markdownExport: true, pasteFix: false, projectMove: true, threadIdBadge: false, conversationView: false, conversationViewMaxWidth: conversationViewDefaultWidth, threadScrollRestore: true, zedRemoteOpen: true, upstreamWorktreeCreate: true, nativeMenuPlacement: true, serviceTierControls: false }; + return { pluginMarketplaceUnlock: true, forcePluginInstall: true, pluginAutoExpand: true, modelWhitelistUnlock: true, sessionDelete: true, markdownExport: true, pasteFix: false, projectMove: true, threadIdBadge: false, conversationView: false, conversationViewMaxWidth: conversationViewDefaultWidth, threadScrollRestore: true, zedRemoteOpen: true, upstreamWorktreeCreate: true, nativeMenuPlacement: true, serviceTierControls: true }; } const codexPlusBackendSettingMap = { @@ -974,6 +974,7 @@ settings.pluginMarketplaceUnlock = false; settings.forcePluginInstall = false; } + settings.serviceTierControls = true; return settings; } catch { const settings = { ...defaultCodexPlusSettings(), ...backendCodexPlusSettings() }; @@ -981,6 +982,7 @@ settings.pluginMarketplaceUnlock = false; settings.forcePluginInstall = false; } + settings.serviceTierControls = true; return settings; } } @@ -1736,10 +1738,7 @@ function applyCodexServiceTierRequestOverride(method, params, threadIdHint = "") { const override = codexServiceTierOverrideForRequest(method, params, threadIdHint); if (!override) return params; - const nextParams = { ...(params || {}), serviceTier: override.serviceTier }; - if (Object.prototype.hasOwnProperty.call(nextParams, "service_tier") || override.fastBlocked) { - nextParams.service_tier = override.serviceTier; - } + const nextParams = { ...(params || {}), serviceTier: override.serviceTier, service_tier: override.serviceTier }; sendCodexPlusDiagnostic("service_tier_request_override_applied", { method, threadId: override.threadId || "", @@ -2163,8 +2162,8 @@
-
Fast 按钮
显示服务模式切换按钮;Fast 仅支持 ${codexServiceTierFastModelListLabel()},其他模型按 Standard 发送。
- +
系统 Fast 开关
是否开启系统 Fast 开关:已默认开启,API Key 登录复用 Codex 原生速度选项与标识;具体 Fast / Standard 在 Codex 界面选择,Fast 仅支持 ${codexServiceTierFastModelListLabel()}。
+
服务模式
继承使用 config.toml 的 service tier;全局模式覆盖全部 thread;自定义允许按 thread 覆盖。
diff --git a/crates/codex-plus-core/src/launcher.rs b/crates/codex-plus-core/src/launcher.rs index 2567aac78..d643804e9 100644 --- a/crates/codex-plus-core/src/launcher.rs +++ b/crates/codex-plus-core/src/launcher.rs @@ -1,3 +1,5 @@ +use std::env; +use std::ffi::OsString; use std::net::SocketAddr; use std::path::{Path, PathBuf}; use std::process::Stdio; @@ -481,6 +483,67 @@ fn helper_bind_host() -> String { .unwrap_or_else(|| "127.0.0.1".to_string()) } +struct ServiceTierPreloadEnv { + node_options: String, +} + +fn prepare_service_tier_preload( + settings: &BackendSettings, +) -> anyhow::Result> { + if settings.enhancements_enabled && settings.codex_app_service_tier_controls { + let preload_path = crate::service_tier_preload::ensure_service_tier_preload() + .context("failed to prepare service tier preload")?; + let node_options = crate::service_tier_preload::node_options_with_service_tier_preload( + env::var("NODE_OPTIONS").ok().as_deref(), + &preload_path.to_string_lossy(), + ); + let _ = crate::diagnostic_log::append_diagnostic_log( + "launcher.service_tier_preload_enabled", + serde_json::json!({ + "preload_path": preload_path.to_string_lossy(), + "node_options": node_options, + }), + ); + Ok(Some(ServiceTierPreloadEnv { node_options })) + } else { + let _ = crate::diagnostic_log::append_diagnostic_log( + "launcher.service_tier_preload_disabled", + serde_json::json!({ + "enhancements_enabled": settings.enhancements_enabled, + "service_tier_controls": settings.codex_app_service_tier_controls, + }), + ); + Ok(None) + } +} + +struct ScopedEnvVar { + key: &'static str, + previous: Option, +} + +impl ScopedEnvVar { + fn set(key: &'static str, value: &str) -> Self { + let previous = env::var_os(key); + unsafe { + env::set_var(key, value); + } + Self { key, previous } + } +} + +impl Drop for ScopedEnvVar { + fn drop(&mut self) { + unsafe { + if let Some(previous) = &self.previous { + env::set_var(self.key, previous); + } else { + env::remove_var(self.key); + } + } + } +} + #[async_trait(?Send)] impl LaunchHooks for DefaultLaunchHooks { fn resolve_app_dir( @@ -640,6 +703,7 @@ impl LaunchHooks for DefaultLaunchHooks { let native_menu_localization_enabled = settings.codex_app_native_menu_localization; let native_menu_inspector_port = native_menu_localization_enabled.then(|| select_native_menu_inspector_port(debug_port)); + let service_tier_preload = prepare_service_tier_preload(settings)?; if cfg!(windows) { let activation = if let Some(inspector_port) = native_menu_inspector_port { build_packaged_activation_with_native_menu_inspector( @@ -660,6 +724,9 @@ impl LaunchHooks for DefaultLaunchHooks { else { unreachable!(); }; + let _node_options_guard = service_tier_preload + .as_ref() + .map(|preload| ScopedEnvVar::set("NODE_OPTIONS", &preload.node_options)); let process_id = activate_packaged_app(app_user_model_id, arguments).await?; if let Some(inspector_port) = native_menu_inspector_port { start_native_menu_localizer(inspector_port); @@ -697,11 +764,16 @@ impl LaunchHooks for DefaultLaunchHooks { }; let executable = command .first() - .ok_or_else(|| anyhow::anyhow!("macOS open command is empty"))?; - let child = Command::new(executable) + .ok_or_else(|| anyhow::anyhow!("macOS Codex command is empty"))?; + let mut child_command = Command::new(executable); + child_command .args(&command[1..]) .stdout(Stdio::null()) - .stderr(Stdio::null()) + .stderr(Stdio::null()); + if let Some(preload) = &service_tier_preload { + child_command.env("NODE_OPTIONS", &preload.node_options); + } + let child = child_command .spawn() .context("failed to launch macOS Codex app")?; *self.child.lock().await = Some(child); @@ -710,7 +782,7 @@ impl LaunchHooks for DefaultLaunchHooks { } return Ok(CodexLaunch::Process { command, - wait_strategy: ProcessWaitStrategy::ExternalWaitCommand, + wait_strategy: ProcessWaitStrategy::TrackedChild, macos_cleanup_policy: Some(cleanup_policy), }); } @@ -733,6 +805,9 @@ impl LaunchHooks for DefaultLaunchHooks { .args(&command[1..]) .stdout(Stdio::null()) .stderr(Stdio::null()); + if let Some(preload) = &service_tier_preload { + child_command.env("NODE_OPTIONS", &preload.node_options); + } #[cfg(windows)] child_command.creation_flags(crate::windows_integration::CREATE_NO_WINDOW); let child = child_command diff --git a/crates/codex-plus-core/src/lib.rs b/crates/codex-plus-core/src/lib.rs index 626898906..c28ebb4df 100644 --- a/crates/codex-plus-core/src/lib.rs +++ b/crates/codex-plus-core/src/lib.rs @@ -29,6 +29,7 @@ pub mod relay_rotation; pub mod relay_switch; pub mod routes; pub mod script_market; +pub mod service_tier_preload; pub mod settings; pub mod status; pub mod update; diff --git a/crates/codex-plus-core/src/service_tier_preload.rs b/crates/codex-plus-core/src/service_tier_preload.rs new file mode 100644 index 000000000..58f6fea1e --- /dev/null +++ b/crates/codex-plus-core/src/service_tier_preload.rs @@ -0,0 +1,235 @@ +use std::fs; +use std::path::PathBuf; + +use anyhow::Context; + +const PRELOAD_FILE: &str = "service-tier-preload.js"; + +pub fn ensure_service_tier_preload() -> anyhow::Result { + let dir = crate::paths::default_app_state_dir().join("preload"); + fs::create_dir_all(&dir).with_context(|| { + format!( + "failed to create service tier preload directory {}", + dir.display() + ) + })?; + let path = dir.join(PRELOAD_FILE); + fs::write(&path, service_tier_preload_script()).with_context(|| { + format!( + "failed to write service tier preload script {}", + path.display() + ) + })?; + Ok(path) +} + +pub fn node_options_with_service_tier_preload( + existing: Option<&str>, + preload_path: &str, +) -> String { + let require_arg = format!("--require={preload_path}"); + match existing.map(str::trim).filter(|value| !value.is_empty()) { + Some(existing) if existing.contains(&require_arg) => existing.to_string(), + Some(existing) => format!("{require_arg} {existing}"), + None => require_arg, + } +} + +pub fn service_tier_preload_script() -> &'static str { + r#""use strict"; + +const fs = require("fs"); +const path = require("path"); +const Module = require("module"); + +const PATCH_MARK = Symbol.for("codex-plus.service-tier-protocol-handle-patched"); +const PATCH_VERSION = "protocol-handle-2"; +const SERVICE_TIER_SETTINGS_ASSET_RE = /^use-service-tier-settings-.*\.js$/; +const READ_SERVICE_TIER_ASSET_RE = /^read-service-tier-for-request-.*\.js$/; +const LOG_PATH = path.join(process.env.HOME || process.cwd(), ".codex-session-delete", "codex-plus.log"); +const SETTINGS_PATH = path.join(process.env.HOME || process.cwd(), ".codex-session-delete", "settings.json"); + +function log(event, detail) { + try { + fs.mkdirSync(path.dirname(LOG_PATH), { recursive: true }); + fs.appendFileSync(LOG_PATH, JSON.stringify({ + timestamp_ms: Date.now(), + pid: process.pid, + event, + detail: detail || {}, + }) + "\n"); + } catch {} +} + +function patchServiceTierSettingsAsset(source) { + let patched = source; + patched = replaceOnce( + patched, + "s=o?.authMethod===`chatgpt`", + "s=o?.authMethod===`chatgpt`||o?.authMethod===`apikey`", + "service tier settings auth gate" + ); + patched = replaceOnce( + patched, + "s&&!f&&u!=null", + "s&&!f", + "service tier settings API key config requirement" + ); + return patched; +} + +function patchReadServiceTierAsset(source) { + let patched = source; + patched = replaceOnce( + patched, + "return n===`chatgpt`?(await e.query.fetch(c,{authMethod:n,hostId:t})).requirements?.featureRequirements?.fast_mode!==!1:!1", + "return n===`chatgpt`?(await e.query.fetch(c,{authMethod:n,hostId:t})).requirements?.featureRequirements?.fast_mode!==!1:n===`apikey`", + "read service tier auth gate" + ); + patched = replaceOnce( + patched, + "return d.service_tier==null?t(await m(o,c??d.model),d.service_tier,s):t(null,d.service_tier,s)", + "return d.service_tier==null?t(await m(o,c??d.model),d.service_tier,s):t(await m(o,c??d.model),d.service_tier,s)", + "read service tier explicit config model lookup" + ); + return patched; +} + +function replaceOnce(source, from, to, label) { + if (source.includes(to)) return source; + if (!source.includes(from)) throw new Error(`${label} pattern not found`); + return source.replace(from, to); +} + +function appProtocolAssetName(url) { + if (typeof url !== "string") return ""; + try { + const parsed = new URL(url); + if (parsed.protocol !== "app:" || parsed.host !== "-") return ""; + const segments = decodeURIComponent(parsed.pathname).split("/").filter(Boolean); + return segments.length >= 2 && segments[0] === "assets" ? segments[segments.length - 1] : ""; + } catch { + return ""; + } +} + +function serviceTierControlsEnabled() { + try { + const settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, "utf8")); + return settings && settings.enhancementsEnabled !== false; + } catch { + return true; + } +} + +function discoverPatchedAssets() { + if (!serviceTierControlsEnabled()) { + log("service_tier_preload_disabled_by_settings", {}); + return new Map(); + } + const assetsDir = path.join(process.resourcesPath, "app.asar", "webview", "assets"); + const assets = new Map(); + for (const name of fs.readdirSync(assetsDir)) { + const filePath = path.join(assetsDir, name); + if (SERVICE_TIER_SETTINGS_ASSET_RE.test(name)) { + assets.set(name, { + kind: "native-service-tier-settings", + patched: patchServiceTierSettingsAsset(fs.readFileSync(filePath, "utf8")), + }); + } else if (READ_SERVICE_TIER_ASSET_RE.test(name)) { + assets.set(name, { + kind: "native-read-service-tier", + patched: patchReadServiceTierAsset(fs.readFileSync(filePath, "utf8")), + }); + } + } + if (assets.size === 0) throw new Error("target native speed UI assets were not found"); + return assets; +} + +function installProtocolHandlePatch(electron) { + const protocol = electron && electron.protocol; + if (!protocol || typeof protocol.handle !== "function") { + log("service_tier_preload_protocol_unavailable", {}); + return; + } + if (protocol.handle[PATCH_MARK] === PATCH_VERSION) return; + + const patchedAssets = discoverPatchedAssets(); + if (patchedAssets.size === 0) return; + const originalHandle = protocol.handle; + const wrappedHandle = function codexPlusServiceTierProtocolHandle(scheme, handler) { + if (String(scheme) !== "app" || typeof handler !== "function") { + return originalHandle.apply(this, arguments); + } + const wrappedHandler = async function codexPlusServiceTierAppProtocolHandler(request) { + const asset = patchedAssets.get(appProtocolAssetName(request && request.url)); + if (!asset) return handler.call(this, request); + log("service_tier_preload_asset_patched", { kind: asset.kind, url: request && request.url, version: PATCH_VERSION }); + return new Response(Buffer.from(asset.patched, "utf8"), { + headers: { + "Content-Length": String(Buffer.byteLength(asset.patched, "utf8")), + "Content-Type": "text/javascript; charset=utf-8", + "X-Codex-Plus-Patch": PATCH_VERSION, + }, + }); + }; + return originalHandle.call(this, scheme, wrappedHandler); + }; + + Object.defineProperty(wrappedHandle, PATCH_MARK, { + configurable: false, + enumerable: false, + value: PATCH_VERSION, + }); + protocol.handle = wrappedHandle; + log("service_tier_preload_protocol_patch_installed", { + assets: Array.from(patchedAssets.keys()), + version: PATCH_VERSION, + }); +} + +const originalLoad = Module._load; +Module._load = function codexPlusServiceTierModuleLoad(request, parent, isMain) { + const result = originalLoad.apply(this, arguments); + if (request === "electron") { + try { + installProtocolHandlePatch(result); + } catch (error) { + log("service_tier_preload_protocol_patch_failed", { message: String(error) }); + } + } + return result; +}; + +log("service_tier_preload_loaded", { version: PATCH_VERSION }); +"# +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn node_options_prepends_preload() { + assert_eq!( + node_options_with_service_tier_preload(Some("--trace-warnings"), "/tmp/preload.js"), + "--require=/tmp/preload.js --trace-warnings" + ); + } + + #[test] + fn preload_script_wraps_electron_module_load_and_app_protocol() { + let script = service_tier_preload_script(); + + assert!(script.contains("Module._load")); + assert!(script.contains("protocol.handle")); + assert!(script.contains("serviceTierControlsEnabled")); + assert!(script.contains("settings.enhancementsEnabled !== false")); + assert!(script.contains("service_tier_preload_disabled_by_settings")); + assert!(script.contains("use-service-tier-settings-")); + assert!(script.contains("read-service-tier-for-request-")); + assert!(script.contains("s=o?.authMethod===`chatgpt`||o?.authMethod===`apikey`")); + assert!(script.contains("n===`apikey`")); + } +} diff --git a/crates/codex-plus-core/src/settings.rs b/crates/codex-plus-core/src/settings.rs index 0d2ea6fa9..c6f2a1f08 100644 --- a/crates/codex-plus-core/src/settings.rs +++ b/crates/codex-plus-core/src/settings.rs @@ -235,7 +235,7 @@ pub struct BackendSettings { pub codex_app_native_menu_placement: bool, #[serde(rename = "codexAppNativeMenuLocalization", default = "default_true")] pub codex_app_native_menu_localization: bool, - #[serde(rename = "codexAppServiceTierControls", default)] + #[serde(rename = "codexAppServiceTierControls", default = "default_true")] pub codex_app_service_tier_controls: bool, #[serde(rename = "codexAppImageOverlayEnabled", default)] pub codex_app_image_overlay_enabled: bool, @@ -325,7 +325,7 @@ impl Default for BackendSettings { codex_app_upstream_worktree_create: true, codex_app_native_menu_placement: true, codex_app_native_menu_localization: true, - codex_app_service_tier_controls: false, + codex_app_service_tier_controls: true, codex_app_image_overlay_enabled: false, codex_app_image_overlay_path: String::new(), codex_app_image_overlay_opacity: default_image_overlay_opacity(), @@ -932,6 +932,7 @@ fn normalize_settings_config_sections(mut settings: BackendSettings) -> BackendS } settings.codex_app_image_overlay_opacity = clamp_image_overlay_opacity(settings.codex_app_image_overlay_opacity); + settings.codex_app_service_tier_controls = true; settings } diff --git a/crates/codex-plus-core/tests/cdp_bridge.rs b/crates/codex-plus-core/tests/cdp_bridge.rs index fb172b95f..7bbc91c0f 100644 --- a/crates/codex-plus-core/tests/cdp_bridge.rs +++ b/crates/codex-plus-core/tests/cdp_bridge.rs @@ -477,8 +477,8 @@ fn injection_script_exposes_fast_service_tier_control() { assert!(script.contains("codexServiceTierMaybeLoadModelCatalog")); assert!(script.contains("fastBlocked")); assert!(script.contains("data-tier=\"unsupported\"")); - assert!(script.contains("nextParams.service_tier = override.serviceTier")); - assert!(script.contains("serviceTierControls: false")); + assert!(script.contains("service_tier: override.serviceTier")); + assert!(script.contains("serviceTierControls: true")); assert!(script.contains("data-codex-plus-setting=\"serviceTierControls\"")); assert!(script.contains("data-codex-service-tier-controls")); assert!(script.contains("removeCodexServiceTierBadges")); @@ -559,6 +559,7 @@ fn injection_script_applies_fast_service_tier_contract() { ); assert_eq!(cases["turnWithoutModel"]["serviceTier"], "priority"); + assert_eq!(cases["turnWithoutModel"]["service_tier"], "priority"); assert_eq!(cases["turnWithoutModelDiagnosticModel"], "gpt-5.4"); assert_eq!( @@ -571,6 +572,7 @@ fn injection_script_applies_fast_service_tier_contract() { ); assert_eq!(cases["startConversation"]["serviceTier"], "priority"); + assert_eq!(cases["startConversation"]["service_tier"], "priority"); } fn run_service_tier_contract_harness() -> serde_json::Value { diff --git a/crates/codex-plus-core/tests/launcher.rs b/crates/codex-plus-core/tests/launcher.rs index a47c08e81..b999e144a 100644 --- a/crates/codex-plus-core/tests/launcher.rs +++ b/crates/codex-plus-core/tests/launcher.rs @@ -210,10 +210,15 @@ fn launcher_builds_debug_arguments_and_commands() { } #[test] -fn launcher_does_not_override_codex_app_environment() { +fn launcher_uses_gated_startup_preload_without_proxy_environment_override() { let source = include_str!("../src/launcher.rs"); - assert!(!source.contains(".envs(codex_process_environment())")); + assert!(source.contains("NODE_OPTIONS")); + assert!(source.contains("ensure_service_tier_preload")); + assert!(source.contains("ScopedEnvVar::set(\"NODE_OPTIONS\"")); + assert!(source.contains("settings.enhancements_enabled")); + assert!(source.contains("settings.codex_app_service_tier_controls")); + assert!(source.contains("launcher.service_tier_preload_disabled")); assert!(!source.contains("activate_packaged_app_with_environment")); assert!(!source.contains("with_temporary_proxy_environment")); } From c941434296f5bffebe3a7ae8c4fc03355f056886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B1=86=E5=8C=85?= Date: Thu, 18 Jun 2026 13:10:15 +0800 Subject: [PATCH 2/7] fix: align CI release build prerequisites --- .github/workflows/pr-build.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index ce3818db7..ebc0f2678 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -38,7 +38,10 @@ jobs: - name: Build frontend working-directory: apps/codex-plus-manager - run: npm run vite:build + shell: bash + run: | + ./node_modules/.bin/vite build + test -d dist - name: Rust tests run: cargo test --workspace @@ -116,7 +119,10 @@ jobs: - name: Build frontend working-directory: apps/codex-plus-manager - run: npm run vite:build + shell: bash + run: | + ./node_modules/.bin/vite build + test -d dist - name: Build release binaries run: cargo build --release --target ${{ matrix.target }} From aef89c621ebd3ce573495b43213661865941071f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B1=86=E5=8C=85?= Date: Thu, 18 Jun 2026 13:22:45 +0800 Subject: [PATCH 3/7] fix: restore relay profile snapshot guard --- apps/codex-plus-launcher/src/main.rs | 2 +- apps/codex-plus-manager/src/App.tsx | 56 ++++++++++++++++++++++------ 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/apps/codex-plus-launcher/src/main.rs b/apps/codex-plus-launcher/src/main.rs index fab54fa92..5918c62ad 100644 --- a/apps/codex-plus-launcher/src/main.rs +++ b/apps/codex-plus-launcher/src/main.rs @@ -149,7 +149,7 @@ async fn activate_existing_codex_app(options: &LaunchOptions) -> anyhow::Result< hooks.start_helper(options.helper_port).await?; } let process_ids = codex_plus_core::watcher::find_codex_processes(); - let mut activated = false; + let activated = false; #[cfg(windows)] { for process_id in &process_ids { diff --git a/apps/codex-plus-manager/src/App.tsx b/apps/codex-plus-manager/src/App.tsx index ab7ce048f..adf7af8ff 100644 --- a/apps/codex-plus-manager/src/App.tsx +++ b/apps/codex-plus-manager/src/App.tsx @@ -1544,6 +1544,16 @@ export function App() { targetRelayName: targetBeforeSnapshot.name, targetRelayMode: targetBeforeSnapshot.relayMode, }); + const switchSettingsWithSnapshot = await snapshotActiveRelayFilesBeforeSwitch(switchSettings, previousActiveRelayId); + if (!switchSettingsWithSnapshot) { + logDiagnostic("switchRelayProfile.snapshot_failed", { + currentRelayId: previousActiveRelayId, + targetRelayId: switchSettings.activeRelayId, + }); + return; + } + switchSettings = switchSettingsWithSnapshot; + const selectedBeforeSave = activeRelayProfile(switchSettings); const validationError = relayProfileSwitchValidation(selectedBeforeSave); if (validationError) { @@ -1555,8 +1565,7 @@ export function App() { showNotice("供应商配置可能不正确", validationError, "failed"); return; } - switchSettings = await snapshotActiveRelayFilesBeforeSwitch(switchSettings, previousActiveRelayId); - const selectedAfterSave = activeRelayProfile(switchSettings); + const selectedAfterSave = selectedBeforeSave; const command = relayProfileSwitchCommand(selectedAfterSave); logDiagnostic("switchRelayProfile.apply_start", { @@ -1618,21 +1627,38 @@ export function App() { const snapshotActiveRelayFilesBeforeSwitch = async ( next: BackendSettings, previousActiveRelayId: string, - ): Promise => { - const profileId = previousActiveRelayId.trim(); - if (!profileId) return next; + ): Promise => { + const current = activeRelayProfile({ ...settingsForm, activeRelayId: previousActiveRelayId }); + const selected = activeRelayProfile(next); + if (current.id === selected.id) return next; + + logDiagnostic("snapshotActiveRelayFilesBeforeSwitch.start", { + currentRelayId: current.id, + currentRelayName: current.name, + selectedRelayId: selected.id, + selectedRelayName: selected.name, + }); const result = await run(() => call("backfill_relay_profile_from_live", { - request: { settings: next, profileId }, + request: { settings: next, profileId: current.id }, }), ); - if (!result) return next; - const normalized = normalizeSettings(result.settings); - if (!isSuccessStatus(result.status)) { - showNotice("供应商切换", result.message, result.status); - return next; + if (!result || !isSuccessStatus(result.status)) { + logDiagnostic("snapshotActiveRelayFilesBeforeSwitch.failed", { + currentRelayId: current.id, + selectedRelayId: selected.id, + status: result?.status, + message: result?.message, + }); + showNotice("供应商切换", result?.message ?? "读取当前配置文件失败,已停止切换以避免覆盖用户改动。", result?.status ?? "failed"); + return null; } - return normalized; + + logDiagnostic("snapshotActiveRelayFilesBeforeSwitch.ok", { + currentRelayId: current.id, + selectedRelayId: selected.id, + }); + return syncLegacyRelayFields(normalizeSettings(result.settings)); }; const copyText = async (text: string, message: string) => { @@ -6073,6 +6099,12 @@ function relayProfileModeSwitchedText(profile: RelayProfile): string { return "已按此供应商切回官方登录;Codex增强已设为兼容增强。"; } +function relayProfileSwitchCommand(profile: RelayProfile): "clear_relay_injection" | "apply_relay_injection" | "apply_pure_api_injection" { + if (profile.relayMode === "pureApi") return "apply_pure_api_injection"; + if (profile.relayMode === "official" && !profile.officialMixApiKey) return "clear_relay_injection"; + return "apply_relay_injection"; +} + function withGeneratedRelayFiles(profile: RelayProfile): RelayProfile { if (isAggregateRelayProfile(profile)) { return { ...profile, configContents: "", authContents: "", aggregate: normalizeAggregateConfig(profile.aggregate, []) }; From 150da11fce9d99a90fd72e193ea7b56c50d57e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B1=86=E5=8C=85?= Date: Thu, 18 Jun 2026 13:35:22 +0800 Subject: [PATCH 4/7] fix: keep launcher activation flag mutable on Windows --- apps/codex-plus-launcher/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/codex-plus-launcher/src/main.rs b/apps/codex-plus-launcher/src/main.rs index 5918c62ad..fab54fa92 100644 --- a/apps/codex-plus-launcher/src/main.rs +++ b/apps/codex-plus-launcher/src/main.rs @@ -149,7 +149,7 @@ async fn activate_existing_codex_app(options: &LaunchOptions) -> anyhow::Result< hooks.start_helper(options.helper_port).await?; } let process_ids = codex_plus_core::watcher::find_codex_processes(); - let activated = false; + let mut activated = false; #[cfg(windows)] { for process_id in &process_ids { From 54f28232136050a6157096cacb5a52b10af9c3d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B1=86=E5=8C=85?= Date: Mon, 22 Jun 2026 11:00:23 +0800 Subject: [PATCH 5/7] fix: harden native fast service tier patch --- apps/codex-plus-launcher/src/main.rs | 3 + .../src/service_tier_preload.rs | 158 +++++++++++++----- crates/codex-plus-core/src/settings.rs | 1 - 3 files changed, 115 insertions(+), 47 deletions(-) diff --git a/apps/codex-plus-launcher/src/main.rs b/apps/codex-plus-launcher/src/main.rs index fab54fa92..0d3dd5224 100644 --- a/apps/codex-plus-launcher/src/main.rs +++ b/apps/codex-plus-launcher/src/main.rs @@ -149,6 +149,9 @@ async fn activate_existing_codex_app(options: &LaunchOptions) -> anyhow::Result< hooks.start_helper(options.helper_port).await?; } let process_ids = codex_plus_core::watcher::find_codex_processes(); + #[cfg(not(windows))] + let activated = false; + #[cfg(windows)] let mut activated = false; #[cfg(windows)] { diff --git a/crates/codex-plus-core/src/service_tier_preload.rs b/crates/codex-plus-core/src/service_tier_preload.rs index 58f6fea1e..467a704df 100644 --- a/crates/codex-plus-core/src/service_tier_preload.rs +++ b/crates/codex-plus-core/src/service_tier_preload.rs @@ -43,7 +43,7 @@ const path = require("path"); const Module = require("module"); const PATCH_MARK = Symbol.for("codex-plus.service-tier-protocol-handle-patched"); -const PATCH_VERSION = "protocol-handle-2"; +const PATCH_VERSION = "protocol-handle-3"; const SERVICE_TIER_SETTINGS_ASSET_RE = /^use-service-tier-settings-.*\.js$/; const READ_SERVICE_TIER_ASSET_RE = /^read-service-tier-for-request-.*\.js$/; const LOG_PATH = path.join(process.env.HOME || process.cwd(), ".codex-session-delete", "codex-plus.log"); @@ -63,16 +63,26 @@ function log(event, detail) { function patchServiceTierSettingsAsset(source) { let patched = source; - patched = replaceOnce( + patched = replaceFirstOf( patched, - "s=o?.authMethod===`chatgpt`", - "s=o?.authMethod===`chatgpt`||o?.authMethod===`apikey`", + [ + [ + "s=o?.authMethod===`chatgpt`", + "s=o?.authMethod===`chatgpt`||o?.authMethod===`apikey`", + ], + [ + "c=o?.authMethod===`chatgpt`", + "c=o?.authMethod===`chatgpt`||o?.authMethod===`apikey`", + ], + ], "service tier settings auth gate" ); - patched = replaceOnce( + patched = replaceFirstOf( patched, - "s&&!f&&u!=null", - "s&&!f", + [ + ["s&&!f&&u!=null", "s&&!f"], + ["c&&!p&&d!=null", "c&&!p"], + ], "service tier settings API key config requirement" ); return patched; @@ -80,16 +90,28 @@ function patchServiceTierSettingsAsset(source) { function patchReadServiceTierAsset(source) { let patched = source; - patched = replaceOnce( + patched = replaceFirstOf( patched, - "return n===`chatgpt`?(await e.query.fetch(c,{authMethod:n,hostId:t})).requirements?.featureRequirements?.fast_mode!==!1:!1", - "return n===`chatgpt`?(await e.query.fetch(c,{authMethod:n,hostId:t})).requirements?.featureRequirements?.fast_mode!==!1:n===`apikey`", + [ + [ + "return n===`chatgpt`?(await e.query.fetch(c,{authMethod:n,hostId:t})).requirements?.featureRequirements?.fast_mode!==!1:!1", + "return n===`chatgpt`?(await e.query.fetch(c,{authMethod:n,hostId:t})).requirements?.featureRequirements?.fast_mode!==!1:n===`apikey`", + ], + ], "read service tier auth gate" ); - patched = replaceOnce( + patched = replaceFirstOf( patched, - "return d.service_tier==null?t(await m(o,c??d.model),d.service_tier,s):t(null,d.service_tier,s)", - "return d.service_tier==null?t(await m(o,c??d.model),d.service_tier,s):t(await m(o,c??d.model),d.service_tier,s)", + [ + [ + "return d.service_tier==null?t(await m(o,c??d.model),d.service_tier,s):t(null,d.service_tier,s)", + "return d.service_tier==null?t(await m(o,c??d.model),d.service_tier,s):t(await m(o,c??d.model),d.service_tier,s)", + ], + [ + "return d.service_tier==null?i(await m(o,c??d.model),d.service_tier,s):i(null,d.service_tier,s)", + "return d.service_tier==null?i(await m(o,c??d.model),d.service_tier,s):i(await m(o,c??d.model),d.service_tier,s)", + ], + ], "read service tier explicit config model lookup" ); return patched; @@ -101,6 +123,36 @@ function replaceOnce(source, from, to, label) { return source.replace(from, to); } +function replaceFirstOf(source, replacements, label) { + for (const [from, to] of replacements) { + if (source.includes(from)) return source.replace(from, to); + } + for (const [, to] of replacements) { + if (source.includes(to)) return source; + } + throw new Error(`${label} pattern not found`); +} + +function tryPatchServiceTierAsset(kind, source, url) { + try { + return { + ok: true, + source: patchServiceTierAsset(kind, source), + }; + } catch (error) { + log("service_tier_preload_asset_patch_failed", { + kind, + url, + message: String(error), + version: PATCH_VERSION, + }); + return { + ok: false, + source, + }; + } +} + function appProtocolAssetName(url) { if (typeof url !== "string") return ""; try { @@ -122,29 +174,47 @@ function serviceTierControlsEnabled() { } } -function discoverPatchedAssets() { +function serviceTierAssetKindFromUrl(url) { + const name = appProtocolAssetName(url); + if (SERVICE_TIER_SETTINGS_ASSET_RE.test(name)) return "native-service-tier-settings"; + if (READ_SERVICE_TIER_ASSET_RE.test(name)) return "native-read-service-tier"; + return ""; +} + +function patchServiceTierAsset(kind, source) { + if (kind === "native-service-tier-settings") return patchServiceTierSettingsAsset(source); + if (kind === "native-read-service-tier") return patchReadServiceTierAsset(source); + return source; +} + +function responseHeadersWithPatchMark(response, source) { + const headers = new Headers(response && response.headers ? response.headers : undefined); + headers.set("Content-Length", String(Buffer.byteLength(source, "utf8"))); + headers.set("Content-Type", "text/javascript; charset=utf-8"); + headers.set("X-Codex-Plus-Patch", PATCH_VERSION); + return headers; +} + +async function patchedAssetResponse(kind, request, handler, self) { + const response = await handler.call(self, request); + const source = await response.text(); + const result = tryPatchServiceTierAsset(kind, source, request && request.url); + if (result.ok) { + log("service_tier_preload_asset_patched", { kind, url: request && request.url, version: PATCH_VERSION }); + } + return new Response(Buffer.from(result.source, "utf8"), { + status: response.status, + statusText: response.statusText, + headers: result.ok ? responseHeadersWithPatchMark(response, result.source) : response.headers, + }); +} + +function shouldInstallProtocolPatch() { if (!serviceTierControlsEnabled()) { log("service_tier_preload_disabled_by_settings", {}); - return new Map(); - } - const assetsDir = path.join(process.resourcesPath, "app.asar", "webview", "assets"); - const assets = new Map(); - for (const name of fs.readdirSync(assetsDir)) { - const filePath = path.join(assetsDir, name); - if (SERVICE_TIER_SETTINGS_ASSET_RE.test(name)) { - assets.set(name, { - kind: "native-service-tier-settings", - patched: patchServiceTierSettingsAsset(fs.readFileSync(filePath, "utf8")), - }); - } else if (READ_SERVICE_TIER_ASSET_RE.test(name)) { - assets.set(name, { - kind: "native-read-service-tier", - patched: patchReadServiceTierAsset(fs.readFileSync(filePath, "utf8")), - }); - } + return false; } - if (assets.size === 0) throw new Error("target native speed UI assets were not found"); - return assets; + return true; } function installProtocolHandlePatch(electron) { @@ -154,25 +224,17 @@ function installProtocolHandlePatch(electron) { return; } if (protocol.handle[PATCH_MARK] === PATCH_VERSION) return; + if (!shouldInstallProtocolPatch()) return; - const patchedAssets = discoverPatchedAssets(); - if (patchedAssets.size === 0) return; const originalHandle = protocol.handle; const wrappedHandle = function codexPlusServiceTierProtocolHandle(scheme, handler) { if (String(scheme) !== "app" || typeof handler !== "function") { return originalHandle.apply(this, arguments); } const wrappedHandler = async function codexPlusServiceTierAppProtocolHandler(request) { - const asset = patchedAssets.get(appProtocolAssetName(request && request.url)); - if (!asset) return handler.call(this, request); - log("service_tier_preload_asset_patched", { kind: asset.kind, url: request && request.url, version: PATCH_VERSION }); - return new Response(Buffer.from(asset.patched, "utf8"), { - headers: { - "Content-Length": String(Buffer.byteLength(asset.patched, "utf8")), - "Content-Type": "text/javascript; charset=utf-8", - "X-Codex-Plus-Patch": PATCH_VERSION, - }, - }); + const kind = serviceTierAssetKindFromUrl(request && request.url); + if (!kind) return handler.call(this, request); + return patchedAssetResponse(kind, request, handler, this); }; return originalHandle.call(this, scheme, wrappedHandler); }; @@ -184,7 +246,6 @@ function installProtocolHandlePatch(electron) { }); protocol.handle = wrappedHandle; log("service_tier_preload_protocol_patch_installed", { - assets: Array.from(patchedAssets.keys()), version: PATCH_VERSION, }); } @@ -227,6 +288,11 @@ mod tests { assert!(script.contains("serviceTierControlsEnabled")); assert!(script.contains("settings.enhancementsEnabled !== false")); assert!(script.contains("service_tier_preload_disabled_by_settings")); + assert!(script.contains("patchedAssetResponse")); + assert!(script.contains("tryPatchServiceTierAsset")); + assert!(script.contains("service_tier_preload_asset_patch_failed")); + assert!(script.contains("handler.call(self, request)")); + assert!(!script.contains("app.asar\", \"webview\", \"assets")); assert!(script.contains("use-service-tier-settings-")); assert!(script.contains("read-service-tier-for-request-")); assert!(script.contains("s=o?.authMethod===`chatgpt`||o?.authMethod===`apikey`")); diff --git a/crates/codex-plus-core/src/settings.rs b/crates/codex-plus-core/src/settings.rs index c6f2a1f08..7724ef4b4 100644 --- a/crates/codex-plus-core/src/settings.rs +++ b/crates/codex-plus-core/src/settings.rs @@ -932,7 +932,6 @@ fn normalize_settings_config_sections(mut settings: BackendSettings) -> BackendS } settings.codex_app_image_overlay_opacity = clamp_image_overlay_opacity(settings.codex_app_image_overlay_opacity); - settings.codex_app_service_tier_controls = true; settings } From f2bfb1a3d42f735b9a0671b55011b247e55dcdae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B1=86=E5=8C=85?= Date: Mon, 22 Jun 2026 15:26:17 +0800 Subject: [PATCH 6/7] fix: remove duplicate relay switch command helper --- apps/codex-plus-manager/src/App.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/codex-plus-manager/src/App.tsx b/apps/codex-plus-manager/src/App.tsx index adf7af8ff..5c19eb942 100644 --- a/apps/codex-plus-manager/src/App.tsx +++ b/apps/codex-plus-manager/src/App.tsx @@ -6099,12 +6099,6 @@ function relayProfileModeSwitchedText(profile: RelayProfile): string { return "已按此供应商切回官方登录;Codex增强已设为兼容增强。"; } -function relayProfileSwitchCommand(profile: RelayProfile): "clear_relay_injection" | "apply_relay_injection" | "apply_pure_api_injection" { - if (profile.relayMode === "pureApi") return "apply_pure_api_injection"; - if (profile.relayMode === "official" && !profile.officialMixApiKey) return "clear_relay_injection"; - return "apply_relay_injection"; -} - function withGeneratedRelayFiles(profile: RelayProfile): RelayProfile { if (isAggregateRelayProfile(profile)) { return { ...profile, configContents: "", authContents: "", aggregate: normalizeAggregateConfig(profile.aggregate, []) }; From c6764b9feaf150d2c31d7e90b09af9ec53d68c8a Mon Sep 17 00:00:00 2001 From: yinsang Date: Sun, 28 Jun 2026 13:46:26 +0800 Subject: [PATCH 7/7] fix: adapt native fast preload for current Codex --- crates/codex-plus-core/src/launcher.rs | 61 ++++++++++++++++++- .../src/service_tier_preload.rs | 22 ++++++- crates/codex-plus-core/tests/launcher.rs | 2 + 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/crates/codex-plus-core/src/launcher.rs b/crates/codex-plus-core/src/launcher.rs index d643804e9..5f3f91fdf 100644 --- a/crates/codex-plus-core/src/launcher.rs +++ b/crates/codex-plus-core/src/launcher.rs @@ -544,6 +544,20 @@ impl Drop for ScopedEnvVar { } } +fn apply_service_tier_preload_env(command: &mut Command, preload: &ServiceTierPreloadEnv) { + command.env("NODE_OPTIONS", &preload.node_options); + if env::var_os("HOME").is_none() + && let Some(home) = service_tier_preload_home_dir() + { + command.env("HOME", home); + } +} + +fn service_tier_preload_home_dir() -> Option { + let app_state_dir = crate::paths::default_app_state_dir(); + app_state_dir.parent().map(Path::to_path_buf) +} + #[async_trait(?Send)] impl LaunchHooks for DefaultLaunchHooks { fn resolve_app_dir( @@ -716,6 +730,49 @@ impl LaunchHooks for DefaultLaunchHooks { build_packaged_activation(app_dir, debug_port, extra_args) }; if let Some(activation) = activation { + if let Some(preload) = &service_tier_preload { + let command = if let Some(inspector_port) = native_menu_inspector_port { + build_codex_command_with_native_menu_inspector( + app_dir, + debug_port, + inspector_port, + extra_args, + ) + } else { + build_codex_command(app_dir, debug_port, extra_args) + }; + let executable = command + .first() + .ok_or_else(|| anyhow::anyhow!("Codex command is empty"))?; + let mut child_command = Command::new(executable); + child_command + .args(&command[1..]) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + apply_service_tier_preload_env(&mut child_command, preload); + #[cfg(windows)] + child_command.creation_flags(crate::windows_integration::CREATE_NO_WINDOW); + let child = child_command.spawn().with_context(|| { + format!("failed to launch packaged Codex executable {executable}") + })?; + *self.child.lock().await = Some(child); + let _ = crate::diagnostic_log::append_diagnostic_log( + "launcher.service_tier_preload_direct_packaged_launch", + serde_json::json!({ + "app_dir": app_dir.to_string_lossy(), + "debug_port": debug_port, + "command": &command, + }), + ); + if let Some(inspector_port) = native_menu_inspector_port { + start_native_menu_localizer(inspector_port); + } + return Ok(CodexLaunch::Process { + command, + wait_strategy: ProcessWaitStrategy::TrackedChild, + macos_cleanup_policy: None, + }); + } let CodexLaunch::PackagedActivation { app_user_model_id, arguments, @@ -771,7 +828,7 @@ impl LaunchHooks for DefaultLaunchHooks { .stdout(Stdio::null()) .stderr(Stdio::null()); if let Some(preload) = &service_tier_preload { - child_command.env("NODE_OPTIONS", &preload.node_options); + apply_service_tier_preload_env(&mut child_command, preload); } let child = child_command .spawn() @@ -806,7 +863,7 @@ impl LaunchHooks for DefaultLaunchHooks { .stdout(Stdio::null()) .stderr(Stdio::null()); if let Some(preload) = &service_tier_preload { - child_command.env("NODE_OPTIONS", &preload.node_options); + apply_service_tier_preload_env(&mut child_command, preload); } #[cfg(windows)] child_command.creation_flags(crate::windows_integration::CREATE_NO_WINDOW); diff --git a/crates/codex-plus-core/src/service_tier_preload.rs b/crates/codex-plus-core/src/service_tier_preload.rs index 467a704df..79c5ae8ab 100644 --- a/crates/codex-plus-core/src/service_tier_preload.rs +++ b/crates/codex-plus-core/src/service_tier_preload.rs @@ -43,11 +43,12 @@ const path = require("path"); const Module = require("module"); const PATCH_MARK = Symbol.for("codex-plus.service-tier-protocol-handle-patched"); -const PATCH_VERSION = "protocol-handle-3"; +const PATCH_VERSION = "protocol-handle-4"; const SERVICE_TIER_SETTINGS_ASSET_RE = /^use-service-tier-settings-.*\.js$/; const READ_SERVICE_TIER_ASSET_RE = /^read-service-tier-for-request-.*\.js$/; -const LOG_PATH = path.join(process.env.HOME || process.cwd(), ".codex-session-delete", "codex-plus.log"); -const SETTINGS_PATH = path.join(process.env.HOME || process.cwd(), ".codex-session-delete", "settings.json"); +const HOME_DIR = process.env.HOME || process.env.USERPROFILE || process.cwd(); +const LOG_PATH = path.join(HOME_DIR, ".codex-session-delete", "codex-plus.log"); +const SETTINGS_PATH = path.join(HOME_DIR, ".codex-session-delete", "settings.json"); function log(event, detail) { try { @@ -74,6 +75,10 @@ function patchServiceTierSettingsAsset(source) { "c=o?.authMethod===`chatgpt`", "c=o?.authMethod===`chatgpt`||o?.authMethod===`apikey`", ], + [ + "s=a?.authMethod===`chatgpt`", + "s=a?.authMethod===`chatgpt`||a?.authMethod===`apikey`", + ], ], "service tier settings auth gate" ); @@ -97,6 +102,10 @@ function patchReadServiceTierAsset(source) { "return n===`chatgpt`?(await e.query.fetch(c,{authMethod:n,hostId:t})).requirements?.featureRequirements?.fast_mode!==!1:!1", "return n===`chatgpt`?(await e.query.fetch(c,{authMethod:n,hostId:t})).requirements?.featureRequirements?.fast_mode!==!1:n===`apikey`", ], + [ + "return n===`chatgpt`?(await e.query.fetch(g,{authMethod:n,hostId:t})).requirements?.featureRequirements?.fast_mode!==!1:!1", + "return n===`chatgpt`?(await e.query.fetch(g,{authMethod:n,hostId:t})).requirements?.featureRequirements?.fast_mode!==!1:n===`apikey`", + ], ], "read service tier auth gate" ); @@ -111,6 +120,10 @@ function patchReadServiceTierAsset(source) { "return d.service_tier==null?i(await m(o,c??d.model),d.service_tier,s):i(null,d.service_tier,s)", "return d.service_tier==null?i(await m(o,c??d.model),d.service_tier,s):i(await m(o,c??d.model),d.service_tier,s)", ], + [ + "return s.service_tier==null?d(await T(t,i??s.model),s.service_tier,n):d(null,s.service_tier,n)", + "return s.service_tier==null?d(await T(t,i??s.model),s.service_tier,n):d(await T(t,i??s.model),s.service_tier,n)", + ], ], "read service tier explicit config model lookup" ); @@ -293,9 +306,12 @@ mod tests { assert!(script.contains("service_tier_preload_asset_patch_failed")); assert!(script.contains("handler.call(self, request)")); assert!(!script.contains("app.asar\", \"webview\", \"assets")); + assert!(script.contains("process.env.USERPROFILE")); assert!(script.contains("use-service-tier-settings-")); assert!(script.contains("read-service-tier-for-request-")); assert!(script.contains("s=o?.authMethod===`chatgpt`||o?.authMethod===`apikey`")); + assert!(script.contains("s=a?.authMethod===`chatgpt`||a?.authMethod===`apikey`")); assert!(script.contains("n===`apikey`")); + assert!(script.contains("return s.service_tier==null?d(await T(t,i??s.model),s.service_tier,n):d(await T(t,i??s.model),s.service_tier,n)")); } } diff --git a/crates/codex-plus-core/tests/launcher.rs b/crates/codex-plus-core/tests/launcher.rs index b999e144a..e6b171977 100644 --- a/crates/codex-plus-core/tests/launcher.rs +++ b/crates/codex-plus-core/tests/launcher.rs @@ -214,6 +214,8 @@ fn launcher_uses_gated_startup_preload_without_proxy_environment_override() { let source = include_str!("../src/launcher.rs"); assert!(source.contains("NODE_OPTIONS")); + assert!(source.contains("apply_service_tier_preload_env")); + assert!(source.contains("service_tier_preload_direct_packaged_launch")); assert!(source.contains("ensure_service_tier_preload")); assert!(source.contains("ScopedEnvVar::set(\"NODE_OPTIONS\"")); assert!(source.contains("settings.enhancements_enabled"));