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
12 changes: 10 additions & 2 deletions apps/src/app/platform-mode/use-platform-mode-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,22 @@ const RELOAD_AFTER_SWITCH_STORAGE_KEY =
"codexmanager.platform-mode.reload-after-switch";
const RELOAD_AFTER_SWITCH_EVENT =
"codexmanager:platform-mode-reload-after-switch";
const SAFE_RELOAD_DEFAULT_MIGRATION_KEY =
"codexmanager.platform-mode.reload-safe-default-v2";

let reloadAfterSwitchMemoryValue = true;
let reloadAfterSwitchMemoryValue = false;

function getReloadAfterSwitchPreference(): boolean {
if (typeof window === "undefined") {
return reloadAfterSwitchMemoryValue;
}
try {
if (window.localStorage.getItem(SAFE_RELOAD_DEFAULT_MIGRATION_KEY) !== "1") {
// Older versions enabled process termination by default. Reset that inherited preference
// once; users can explicitly opt in again after the migration.
window.localStorage.setItem(RELOAD_AFTER_SWITCH_STORAGE_KEY, "false");
window.localStorage.setItem(SAFE_RELOAD_DEFAULT_MIGRATION_KEY, "1");
}
const stored = window.localStorage.getItem(RELOAD_AFTER_SWITCH_STORAGE_KEY);
if (stored === "true" || stored === "false") {
reloadAfterSwitchMemoryValue = stored === "true";
Expand Down Expand Up @@ -115,7 +123,7 @@ export function usePlatformModePageState(
const reloadAfterSwitch = useSyncExternalStore(
subscribeToReloadAfterSwitchPreference,
getReloadAfterSwitchPreference,
() => true,
() => false,
);
const browserOrigin = useSyncExternalStore(
() => () => undefined,
Expand Down
2 changes: 2 additions & 0 deletions apps/tests/codex-profile-cache.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ test("平台模式切换透传并持久化 Codex 后台重载开关", async () =
const tauri = await readSource("src-tauri/src/commands/codex_profile.rs");

assert.match(state, /codexmanager\.platform-mode\.reload-after-switch/);
assert.match(state, /reloadAfterSwitchMemoryValue = false/);
assert.match(state, /reload-safe-default-v2/);
assert.match(state, /reloadAfterSwitch,/);
assert.match(sections, /切换后重载 Codex 后台/);
assert.match(client, /reloadAfterSwitch: params\.reloadAfterSwitch/);
Expand Down
45 changes: 23 additions & 22 deletions crates/service/src/codex_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,17 +353,13 @@ pub(crate) fn apply_direct_account(
account_id: Some(account.id.clone()),
api_key_id: None,
gateway_base_url: None,
provider_id: PROVIDER_ID.to_string(),
provider_id: DEFAULT_HISTORY_PROVIDER_ID.to_string(),
previous_model_catalog_json: None,
updated_at: now_ts(),
},
)?;
persist_codex_home(&profile_dir)?;
status_for_profile_after_apply(
&profile_dir,
DEFAULT_HISTORY_PROVIDER_ID,
reload_after_switch,
)
status_for_profile_after_apply(&profile_dir, None, reload_after_switch)
}

pub(crate) fn apply_gateway(
Expand Down Expand Up @@ -432,7 +428,7 @@ pub(crate) fn apply_gateway(
},
)?;
persist_codex_home(&profile_dir)?;
status_for_profile_after_apply(&profile_dir, PROVIDER_ID, reload_after_switch)
status_for_profile_after_apply(&profile_dir, Some(PROVIDER_ID), reload_after_switch)
}

pub(crate) fn restore(codex_home: Option<&str>) -> Result<CodexProfileStatus, String> {
Expand Down Expand Up @@ -814,10 +810,16 @@ fn status_for_profile_with_history_repair(

fn status_for_profile_after_apply(
profile_dir: &Path,
target_provider: &str,
history_provider: Option<&str>,
reload_after_switch: bool,
) -> Result<CodexProfileStatus, String> {
let mut status = status_for_profile_with_history_repair(profile_dir, target_provider)?;
// Direct mode keeps a secret-free `cm` compatibility provider, so existing conversations do
// not need a synchronous full-profile history rewrite. Gateway mode still repairs history so
// existing conversations are routed through the managed provider.
let mut status = match history_provider {
Some(provider) => status_for_profile_with_history_repair(profile_dir, provider)?,
None => status_for_profile(profile_dir)?,
};
status.runtime_reload = Some(if reload_after_switch {
crate::codex_runtime::reload_codex_app_servers(profile_dir)
} else {
Expand Down Expand Up @@ -1971,23 +1973,22 @@ fn patch_config_for_direct(
previous_model_catalog_json: Option<&str>,
) -> Result<String, String> {
let mut doc = parse_config(content.as_deref().unwrap_or(""))?;
if doc
.get("model_provider")
.and_then(Item::as_str)
.is_some_and(|provider| provider == PROVIDER_ID)
{
doc.as_table_mut().remove("model_provider");
doc.as_table_mut()
.insert("model_provider", toml_value(DEFAULT_HISTORY_PROVIDER_ID));
if doc.as_table().get("model_providers").is_none() {
doc.as_table_mut()
.insert("model_providers", Item::Table(Table::new()));
}
if let Some(providers) = doc
let providers = doc
.as_table_mut()
.get_mut("model_providers")
.and_then(Item::as_table_mut)
{
providers.remove(PROVIDER_ID);
if providers.is_empty() {
doc.as_table_mut().remove("model_providers");
}
}
.ok_or_else(|| "config.toml model_providers is not a table".to_string())?;
let mut compatibility_provider = Table::new();
compatibility_provider.insert("name", toml_value("OpenAI compatibility"));
compatibility_provider.insert("wire_api", toml_value("responses"));
compatibility_provider.insert("requires_openai_auth", toml_value(true));
providers.insert(PROVIDER_ID, Item::Table(compatibility_provider));
let managed_catalog_path = managed_catalog_path.to_string_lossy();
let catalog_is_managed = doc
.get("model_catalog_json")
Expand Down
13 changes: 10 additions & 3 deletions crates/service/src/codex_profile_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ fn sqlite_provider(dir: &Path, thread_id: &str) -> String {
}

#[test]
fn direct_config_removes_only_managed_provider() {
fn direct_config_uses_openai_and_keeps_secret_free_legacy_provider() {
let input = r#"
model_provider = "cm"
model = "gpt-5.4"
Expand All @@ -128,6 +128,8 @@ model = "gpt-5.4"
name = "CodexManager"
base_url = "http://localhost:48760/v1"
wire_api = "responses"
experimental_bearer_token = "must-be-removed"
custom_header = "must-also-be-removed"

[model_providers.other]
name = "Other"
Expand All @@ -138,8 +140,13 @@ base_url = "https://example.test/v1"
let output = patch_config_for_direct(Some(input.to_string()), &managed_catalog, None)
.expect("patch direct");

assert!(!output.contains("model_provider = \"cm\""));
assert!(!output.contains("[model_providers.cm]"));
assert!(output.contains("model_provider = \"openai\""));
assert!(output.contains("[model_providers.cm]"));
assert!(output.contains("wire_api = \"responses\""));
assert!(output.contains("requires_openai_auth = true"));
assert!(!output.contains("base_url = \"http://localhost:48760/v1\""));
assert!(!output.contains("experimental_bearer_token"));
assert!(!output.contains("custom_header"));
assert!(output.contains("[model_providers.other]"));
assert!(output.contains("model = \"gpt-5.4\""));
}
Expand Down