diff --git a/src-tauri/resources/codex_models/model-catalog.json b/src-tauri/resources/codex_models/model-catalog.json index 516d0876..97993aa7 100644 --- a/src-tauri/resources/codex_models/model-catalog.json +++ b/src-tauri/resources/codex_models/model-catalog.json @@ -70,8 +70,8 @@ "use_responses_lite": true, "include_skills_usage_instructions": false, "auto_review_model_override": null, - "context_window": 372000, - "max_context_window": 372000, + "context_window": 272000, + "max_context_window": 872000, "auto_compact_token_limit": null, "comp_hash": "3000", "reasoning_summary_format": "experimental", @@ -183,8 +183,8 @@ "use_responses_lite": true, "include_skills_usage_instructions": false, "auto_review_model_override": null, - "context_window": 372000, - "max_context_window": 372000, + "context_window": 272000, + "max_context_window": 872000, "auto_compact_token_limit": null, "comp_hash": "3000", "reasoning_summary_format": "experimental", @@ -294,8 +294,8 @@ "use_responses_lite": true, "include_skills_usage_instructions": false, "auto_review_model_override": null, - "context_window": 372000, - "max_context_window": 372000, + "context_window": 272000, + "max_context_window": 872000, "auto_compact_token_limit": null, "comp_hash": "3000", "reasoning_summary_format": "experimental", diff --git a/src-tauri/src/agents/commands.rs b/src-tauri/src/agents/commands.rs index 23b2337c..cf6bcb4a 100644 --- a/src-tauri/src/agents/commands.rs +++ b/src-tauri/src/agents/commands.rs @@ -850,6 +850,7 @@ pub(crate) async fn apply_agent_config( client: String, model: String, oauth_configuration: bool, + remote_compaction: bool, claude_code_model_mappings: Option, claude_desktop_model_mappings: Option, ) -> Result { @@ -894,6 +895,7 @@ pub(crate) async fn apply_agent_config( models: &prepared.models, codex_catalog: prepared.codex_catalog.as_deref(), oauth_configuration, + remote_compaction, claude_code_model_mappings: claude_code_model_mappings.as_ref(), claude_desktop_model_mappings: claude_desktop_model_mappings.as_ref(), }, @@ -924,6 +926,7 @@ pub(crate) async fn reset_agent_config_to_default( client: String, model: String, oauth_configuration: bool, + remote_compaction: bool, claude_code_model_mappings: Option, claude_desktop_model_mappings: Option, ) -> Result { @@ -967,6 +970,7 @@ pub(crate) async fn reset_agent_config_to_default( models: &prepared.models, codex_catalog: prepared.codex_catalog.as_deref(), oauth_configuration, + remote_compaction, claude_code_model_mappings: claude_code_model_mappings.as_ref(), claude_desktop_model_mappings: claude_desktop_model_mappings.as_ref(), }) @@ -1038,6 +1042,7 @@ pub(crate) async fn set_agent_config_enabled( models: &prepared.models, codex_catalog: prepared.codex_catalog.as_deref(), oauth_configuration: false, + remote_compaction: false, claude_code_model_mappings: claude_code_model_mappings.as_ref(), claude_desktop_model_mappings: claude_desktop_model_mappings.as_ref(), }, @@ -1098,6 +1103,7 @@ pub(crate) async fn update_agent_config( models: &prepared.models, codex_catalog: prepared.codex_catalog.as_deref(), oauth_configuration: false, + remote_compaction: false, claude_code_model_mappings: claude_code_model_mappings.as_ref(), claude_desktop_model_mappings: claude_desktop_model_mappings.as_ref(), }, diff --git a/src-tauri/src/agents/configuration.rs b/src-tauri/src/agents/configuration.rs index e917ce64..3322bd73 100644 --- a/src-tauri/src/agents/configuration.rs +++ b/src-tauri/src/agents/configuration.rs @@ -20,6 +20,7 @@ pub(crate) fn build_agent_updates( models, codex_catalog, oauth_configuration: false, + remote_compaction: false, claude_code_model_mappings: None, claude_desktop_model_mappings: None, }, @@ -38,6 +39,7 @@ pub(crate) fn build_agent_updates_with_oauth( models, codex_catalog, oauth_configuration, + remote_compaction, claude_code_model_mappings, claude_desktop_model_mappings, } = options; @@ -125,6 +127,7 @@ pub(crate) fn build_agent_updates_with_oauth( api_key, model, oauth_configuration, + remote_compaction, ) .or_else(|_| { build_codex_agent_config_with_oauth( @@ -133,6 +136,7 @@ pub(crate) fn build_agent_updates_with_oauth( api_key, model, oauth_configuration, + remote_compaction, ) })?; let mut updates = vec![AgentFileUpdate { @@ -2460,7 +2464,7 @@ pub(crate) fn build_codex_agent_config( api_key: &str, model: &str, ) -> Result { - build_codex_agent_config_with_oauth(existing, base_url, api_key, model, false) + build_codex_agent_config_with_oauth(existing, base_url, api_key, model, false, false) } pub(crate) fn build_codex_agent_config_with_oauth( @@ -2469,6 +2473,7 @@ pub(crate) fn build_codex_agent_config_with_oauth( api_key: &str, model: &str, oauth_configuration: bool, + remote_compaction: bool, ) -> Result { use toml_edit::{value, Document, Item, Table}; @@ -2510,7 +2515,15 @@ pub(crate) fn build_codex_agent_config_with_oauth( .get_mut(MANAGED_AGENT_PROVIDER_ID) .and_then(Item::as_table_mut) .ok_or_else(|| "Codex cpa-gui provider 必须是 TOML 表".to_string())?; - set_codex_table_item(provider, "name", value("EasyCLIProxyAPI")); + // Codex currently uses the exact provider name `OpenAI` as its capability + // marker for Remote Compaction V2. Keep the product name by default because + // not every upstream account or proxy route supports the V2 response item. + let provider_name = if remote_compaction { + CODEX_REMOTE_COMPACTION_PROVIDER_NAME + } else { + CODEX_MANAGED_PROVIDER_NAME + }; + set_codex_table_item(provider, "name", value(provider_name)); set_codex_table_item(provider, "base_url", value(base_url)); set_codex_table_item(provider, "wire_api", value("responses")); set_codex_table_item(provider, "experimental_bearer_token", value(api_key)); diff --git a/src-tauri/src/agents/discovery.rs b/src-tauri/src/agents/discovery.rs index 3aa40bdb..1316bc33 100644 --- a/src-tauri/src/agents/discovery.rs +++ b/src-tauri/src/agents/discovery.rs @@ -427,6 +427,7 @@ pub(crate) fn inspect_pi_provider_status( configuration_synchronized: configured, current_model: current_model.clone(), oauth_configuration: false, + remote_compaction: false, modification_enabled: configured, modification_state: modification_state.to_string(), backup_available: false, @@ -905,21 +906,28 @@ pub(crate) fn inspect_agent_config( let paths = agent_config_paths(client, home); let config_exists = paths.iter().any(|path| path.is_file()); let result = inspect_agent_managed_config(client, &paths, port, api_key).and_then( - |(configured, model, oauth_configuration)| { + |(configured, model, oauth_configuration, remote_compaction)| { if client == AgentClient::Codex && configured { let model = model .as_deref() .ok_or_else(|| "Codex 配置缺少默认模型".to_string())?; validate_codex_catalog_file(&paths[0], model)?; } - Ok((configured, model, oauth_configuration)) + Ok((configured, model, oauth_configuration, remote_compaction)) }, ); - let (configured, current_model, oauth_configuration, config_valid, error) = match result { - Ok((configured, model, oauth_configuration)) => { - (configured, model, oauth_configuration, true, None) + let ( + configured, + current_model, + oauth_configuration, + remote_compaction, + config_valid, + error, + ) = match result { + Ok((configured, model, oauth_configuration, remote_compaction)) => { + (configured, model, oauth_configuration, remote_compaction, true, None) } - Err(error) => (false, None, false, false, Some(error)), + Err(error) => (false, None, false, false, false, Some(error)), }; let executable = find_agent_executable(client, home); // Desktop application executables are not CLIs. Invoking them with @@ -1000,6 +1008,7 @@ pub(crate) fn inspect_agent_config( configuration_synchronized, current_model, oauth_configuration, + remote_compaction, modification_enabled: modification.enabled, modification_state: modification.state, backup_available: modification.backup_available, @@ -1197,24 +1206,24 @@ pub(crate) fn inspect_agent_managed_config( paths: &[PathBuf], port: u16, api_key: &str, -) -> Result<(bool, Option, bool), String> { +) -> Result<(bool, Option, bool, bool), String> { match client { AgentClient::ClaudeCode => inspect_claude_agent_config(&paths[0], port, api_key) - .map(|(configured, model)| (configured, model, false)), + .map(|(configured, model)| (configured, model, false, false)), AgentClient::ClaudeDesktop if client.supported_platform() => { inspect_claude_desktop_agent_config(paths, port, api_key) - .map(|(configured, model)| (configured, model, false)) + .map(|(configured, model)| (configured, model, false, false)) } - AgentClient::ClaudeDesktop => Ok((false, None, false)), + AgentClient::ClaudeDesktop => Ok((false, None, false, false)), AgentClient::Codex => inspect_codex_agent_config(&paths[0], port, api_key), AgentClient::OpenCode => inspect_opencode_agent_config(&paths[0], port, api_key) - .map(|(configured, model)| (configured, model, false)), + .map(|(configured, model)| (configured, model, false, false)), AgentClient::OpenClaw => inspect_openclaw_agent_config(&paths[0], port, api_key) - .map(|(configured, model)| (configured, model, false)), + .map(|(configured, model)| (configured, model, false, false)), AgentClient::Hermes => inspect_hermes_agent_config(&paths[0], port, api_key) - .map(|(configured, model)| (configured, model, false)), + .map(|(configured, model)| (configured, model, false, false)), AgentClient::DeepSeekHarness => inspect_deepseek_harness_config(paths, port, api_key) - .map(|(configured, model)| (configured, model, false)), + .map(|(configured, model)| (configured, model, false, false)), AgentClient::ZCode => { if paths.len() != 2 { return Err("ZCode 配置路径数量无效".to_string()); @@ -1226,12 +1235,13 @@ pub(crate) fn inspect_agent_managed_config( app_configured && cli_configured && models_match, cli_model.or(app_model), false, + false, )) } AgentClient::KimiCode => inspect_kimi_code_agent_config(&paths[0], port, api_key) - .map(|(configured, model)| (configured, model, false)), + .map(|(configured, model)| (configured, model, false, false)), AgentClient::GrokBuild => inspect_grok_build_agent_config(&paths[0], port, api_key) - .map(|(configured, model)| (configured, model, false)), + .map(|(configured, model)| (configured, model, false, false)), } } @@ -3244,9 +3254,9 @@ pub(crate) fn inspect_codex_agent_config( path: &Path, port: u16, api_key: &str, -) -> Result<(bool, Option, bool), String> { +) -> Result<(bool, Option, bool, bool), String> { if !path.is_file() { - return Ok((false, None, false)); + return Ok((false, None, false, false)); } let root: toml::Value = toml::from_str( &fs::read_to_string(path).map_err(|error| format!("读取 Codex 配置失败: {error}"))?, @@ -3266,6 +3276,10 @@ pub(crate) fn inspect_codex_agent_config( .and_then(|provider| provider.get("requires_openai_auth")) .and_then(toml::Value::as_bool) == Some(true); + let remote_compaction = provider + .and_then(|provider| provider.get("name")) + .and_then(toml::Value::as_str) + == Some(CODEX_REMOTE_COMPACTION_PROVIDER_NAME); let auth_path = path.with_file_name("auth.json"); let auth_configured = if oauth_configuration { codex_auth_file_has_oauth_tokens(&auth_path) @@ -3279,7 +3293,10 @@ pub(crate) fn inspect_codex_agent_config( && provider .and_then(|provider| provider.get("name")) .and_then(toml::Value::as_str) - == Some("EasyCLIProxyAPI") + .is_some_and(|name| { + name == CODEX_MANAGED_PROVIDER_NAME + || name == CODEX_REMOTE_COMPACTION_PROVIDER_NAME + }) && provider .and_then(|provider| provider.get("base_url")) .and_then(toml::Value::as_str) @@ -3296,7 +3313,7 @@ pub(crate) fn inspect_codex_agent_config( .map(str::trim) .filter(|value| !value.is_empty()) .map(str::to_string); - Ok((configured, model, oauth_configuration)) + Ok((configured, model, oauth_configuration, remote_compaction)) } pub(crate) fn validate_codex_catalog_file(config_path: &Path, model: &str) -> Result<(), String> { diff --git a/src-tauri/src/agents/state.rs b/src-tauri/src/agents/state.rs index 24dab78a..7917ca58 100644 --- a/src-tauri/src/agents/state.rs +++ b/src-tauri/src/agents/state.rs @@ -669,6 +669,7 @@ pub(crate) fn fresh_agent_contents( models: &models, codex_catalog: None, oauth_configuration: false, + remote_compaction: false, claude_code_model_mappings: None, claude_desktop_model_mappings: None, }, @@ -685,6 +686,7 @@ pub(crate) fn fresh_agent_contents_with_oauth( let AgentConfigurationOptions { models, oauth_configuration, + remote_compaction, claude_code_model_mappings, claude_desktop_model_mappings, .. @@ -719,6 +721,7 @@ pub(crate) fn fresh_agent_contents_with_oauth( api_key, model, oauth_configuration, + remote_compaction, )?]), AgentClient::OpenCode => Ok(vec![build_opencode_agent_config( None, @@ -1537,6 +1540,7 @@ pub(crate) fn apply_agent_configuration( models, codex_catalog, oauth_configuration: false, + remote_compaction: false, claude_code_model_mappings: None, claude_desktop_model_mappings: None, }, @@ -1586,6 +1590,7 @@ pub(crate) fn reset_agent_configuration_to_default( }], codex_catalog, oauth_configuration: false, + remote_compaction: false, claude_code_model_mappings: None, claude_desktop_model_mappings: None, }) @@ -1600,6 +1605,7 @@ pub(crate) struct AgentDefaultConfiguration<'a> { pub(crate) models: &'a [AgentModelOption], pub(crate) codex_catalog: Option<&'a str>, pub(crate) oauth_configuration: bool, + pub(crate) remote_compaction: bool, pub(crate) claude_code_model_mappings: Option<&'a ClaudeDesktopModelMappings>, pub(crate) claude_desktop_model_mappings: Option<&'a ClaudeDesktopModelMappings>, } @@ -1616,6 +1622,7 @@ pub(crate) fn reset_agent_configuration_to_default_with_oauth( models, codex_catalog, oauth_configuration, + remote_compaction, claude_code_model_mappings, claude_desktop_model_mappings, } = request; @@ -1630,6 +1637,7 @@ pub(crate) fn reset_agent_configuration_to_default_with_oauth( models, codex_catalog, oauth_configuration, + remote_compaction, claude_code_model_mappings, claude_desktop_model_mappings, }, @@ -1653,6 +1661,7 @@ pub(crate) fn reset_agent_configuration_to_default_with_oauth( models, codex_catalog, oauth_configuration, + remote_compaction, claude_code_model_mappings, claude_desktop_model_mappings, }, @@ -1824,7 +1833,7 @@ pub(crate) fn enable_agent_modification( Vec::new(), )); } - let (_, current_model, _) = + let (_, current_model, _, _) = inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?; if agent_has_managed_marker(client, &paths)? { if let Some(current_model) = current_model.as_deref() { @@ -1903,7 +1912,7 @@ pub(crate) fn disable_agent_modification( let mut record = match load_agent_record(client, &paths)? { Some(record) => record, None => { - let (_, model, _) = + let (_, model, _, _) = inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?; if !agent_has_managed_marker(client, &paths)? { return Ok(action_result( @@ -1989,7 +1998,7 @@ pub(crate) fn update_agent_modification( let record = match load_agent_record(client, &paths)? { Some(record) => record, None => { - let (_, current_model, _) = + let (_, current_model, _, _) = inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?; if !agent_has_managed_marker(client, &paths)? { return Err("请先应用配置修改".to_string()); diff --git a/src-tauri/src/codex_catalog.rs b/src-tauri/src/codex_catalog.rs index 8604c451..6a19178f 100644 --- a/src-tauri/src/codex_catalog.rs +++ b/src-tauri/src/codex_catalog.rs @@ -812,6 +812,12 @@ mod tests { let gpt_message_prompt = gpt_messages["instructions_template"].as_str().unwrap(); let gpt_message_prompt_body = gpt_message_prompt.split_once("\n\n").unwrap().1; + for slug in ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"] { + let model = &sources.templates[slug].value; + assert_eq!(model["context_window"], 272_000); + assert_eq!(model["max_context_window"], 872_000); + } + for slug in ["deepseek-v4-flash", "deepseek-v4-pro"] { let model = &sources.templates[slug].value; assert_eq!(model["context_window"], 1_000_000); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index e633c4b1..508b78be 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -138,6 +138,8 @@ const DEFAULT_MAX_RETRY_INTERVAL: u32 = 30; const DEFAULT_STREAMING_BOOTSTRAP_RETRIES: u32 = 0; const LEGACY_DEFAULT_MANAGEMENT_SECRET_KEY: &str = "123456"; const MANAGED_AGENT_PROVIDER_ID: &str = "cpa-gui"; +const CODEX_MANAGED_PROVIDER_NAME: &str = "EasyCLIProxyAPI"; +const CODEX_REMOTE_COMPACTION_PROVIDER_NAME: &str = "OpenAI"; const ZCODE_CONFIG_FILE: &str = "config.json"; const KIMI_CODE_CONFIG_FILE: &str = "config.toml"; const GROK_BUILD_CONFIG_FILE: &str = "config.toml"; @@ -980,6 +982,7 @@ struct AgentConfigStatus { configuration_synchronized: bool, current_model: Option, oauth_configuration: bool, + remote_compaction: bool, modification_enabled: bool, modification_state: String, backup_available: bool, @@ -1295,6 +1298,7 @@ struct AgentConfigurationOptions<'a> { models: &'a [AgentModelOption], codex_catalog: Option<&'a str>, oauth_configuration: bool, + remote_compaction: bool, claude_code_model_mappings: Option<&'a ClaudeDesktopModelMappings>, claude_desktop_model_mappings: Option<&'a ClaudeDesktopModelMappings>, } diff --git a/src-tauri/src/tests/agent_configuration.rs b/src-tauri/src/tests/agent_configuration.rs index 5650c243..9c7fde1f 100644 --- a/src-tauri/src/tests/agent_configuration.rs +++ b/src-tauri/src/tests/agent_configuration.rs @@ -294,6 +294,10 @@ fn codex_agent_config_uses_managed_provider_without_losing_comments() { value["model_providers"][MANAGED_AGENT_PROVIDER_ID]["base_url"].as_str(), Some("http://127.0.0.1:8317/v1") ); + assert_eq!( + value["model_providers"][MANAGED_AGENT_PROVIDER_ID]["name"].as_str(), + Some(CODEX_MANAGED_PROVIDER_NAME) + ); assert_eq!( value["model_providers"][MANAGED_AGENT_PROVIDER_ID]["experimental_bearer_token"].as_str(), Some(DEFAULT_API_KEY) @@ -310,6 +314,7 @@ fn codex_oauth_configuration_uses_openai_auth_with_bearer_token() { DEFAULT_API_KEY, "gpt-test", true, + false, ) .unwrap(); let value = toml::from_str::(&rendered).unwrap(); @@ -337,11 +342,33 @@ fn codex_oauth_configuration_uses_openai_auth_with_bearer_token() { r#"{"tokens":{"access_token":"oauth-access-token"}}"#, ) .unwrap(); - let (configured, model, oauth_configuration) = + let (configured, model, oauth_configuration, remote_compaction) = + inspect_codex_agent_config(&path, 8317, DEFAULT_API_KEY).unwrap(); + assert!(configured); + assert_eq!(model.as_deref(), Some("gpt-test")); + assert!(oauth_configuration); + assert!(!remote_compaction); + + let remote = build_codex_agent_config_with_oauth( + Some(&rendered), + "http://127.0.0.1:8317/v1", + DEFAULT_API_KEY, + "gpt-test", + true, + true, + ) + .unwrap(); + assert_ne!(remote, rendered); + assert!(remote.contains(&format!( + "name = \"{CODEX_REMOTE_COMPACTION_PROVIDER_NAME}\"" + ))); + fs::write(&path, remote).unwrap(); + let (configured, model, oauth_configuration, remote_compaction) = inspect_codex_agent_config(&path, 8317, DEFAULT_API_KEY).unwrap(); assert!(configured); assert_eq!(model.as_deref(), Some("gpt-test")); assert!(oauth_configuration); + assert!(remote_compaction); fs::remove_dir_all(home).unwrap(); } @@ -367,6 +394,7 @@ fn codex_api_and_oauth_modes_write_the_same_catalog() { models: &models, codex_catalog: Some(&catalog), oauth_configuration, + remote_compaction: false, claude_code_model_mappings: None, claude_desktop_model_mappings: None, }, @@ -958,7 +986,7 @@ fn zcode_cli_config_sets_main_model_and_both_configs_must_match() { fs::write(&paths[1], &cli_config).unwrap(); assert_eq!( inspect_agent_managed_config(AgentClient::ZCode, &paths, 8317, DEFAULT_API_KEY).unwrap(), - (true, Some("gpt-test".to_string()), false) + (true, Some("gpt-test".to_string()), false, false) ); let mut mismatched = cli_value; @@ -970,7 +998,7 @@ fn zcode_cli_config_sets_main_model_and_both_configs_must_match() { .unwrap(); assert_eq!( inspect_agent_managed_config(AgentClient::ZCode, &paths, 8317, DEFAULT_API_KEY).unwrap(), - (false, Some("deepseek-test".to_string()), false) + (false, Some("deepseek-test".to_string()), false, false) ); fs::remove_dir_all(home).unwrap(); } diff --git a/src-tauri/src/tests/agent_transactions.rs b/src-tauri/src/tests/agent_transactions.rs index 46f2a40d..2d3f3c08 100644 --- a/src-tauri/src/tests/agent_transactions.rs +++ b/src-tauri/src/tests/agent_transactions.rs @@ -68,7 +68,7 @@ custom_option = "keep-original" provider["custom_option"] = toml_edit::value("keep-updated"); fs::write(&path, externally_edited.to_string()).unwrap(); - let (configured, current_model, _) = + let (configured, current_model, _, _) = inspect_codex_agent_config(&path, 8317, DEFAULT_API_KEY).unwrap(); let inspection = inspect_agent_modification( AgentClient::Codex, diff --git a/src/i18n/ja.ts b/src/i18n/ja.ts index 3bb0bd3c..444cf724 100644 --- a/src/i18n/ja.ts +++ b/src/i18n/ja.ts @@ -655,6 +655,8 @@ export const jaOverrides = { 'agents.model.firstSelection': '利用可能なモデル {count} 件。初回は先頭を選択します', 'agents.modify.title': '設定変更', 'agents.modify.oauthConfiguration': 'OAuth ログイン設定方式', + 'agents.modify.remoteCompaction': 'リモート圧縮を有効化(実験的)', + 'agents.modify.remoteCompactionHint': '現在の CPA アップストリームが Codex Remote Compaction V2 に対応している場合のみ有効にしてください。非対応の経路では圧縮時に失敗する可能性があります。', 'agents.claudeDesktopMapping.title': 'Claude Desktop モデル → 上流モデル', 'agents.claudeDesktopMapping.description': '役割ごとに上流モデルとコンテキスト機能を選択します。対応モデルでのみ 1M を有効にしてください。', 'agents.claudeCodeMapping.title': 'Claude Code モデル → 上流モデル', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 0ba13ad9..fa1c4741 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -673,6 +673,8 @@ export const en: Record = { 'agents.model.firstSelection': '{count} models available; the first is selected by default', 'agents.modify.title': 'Configuration Changes', 'agents.modify.oauthConfiguration': 'OAuth sign-in configuration mode', + 'agents.modify.remoteCompaction': 'Enable remote compaction (experimental)', + 'agents.modify.remoteCompactionHint': 'Enable only when the current CPA upstream supports Codex Remote Compaction V2. Incompatible routes may fail when compaction runs.', 'agents.oauthLoginRequired.title': 'Codex sign-in required', 'agents.oauthLoginRequired.enableDescription': 'No valid Codex sign-in credentials were found, so OAuth configuration mode cannot be enabled. Sign in to your ChatGPT account in the Codex app. If the Codex app cannot trigger sign-in, use', 'agents.oauthLoginRequired.enableClearConfiguration': 'Clear Configuration', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index c8bae911..d4ee3ac0 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -671,6 +671,8 @@ export const zhCN = { 'agents.model.firstSelection': '{count} 个可用模型,首次默认选择第一项', 'agents.modify.title': '配置修改', 'agents.modify.oauthConfiguration': 'OAuth登录配置方式', + 'agents.modify.remoteCompaction': '启用远程压缩(实验性)', + 'agents.modify.remoteCompactionHint': '仅当当前 CPA 上游兼容 Codex Remote Compaction V2 时开启;不兼容可能在压缩时导致请求失败。', 'agents.oauthLoginRequired.title': '需要登录 Codex', 'agents.oauthLoginRequired.enableDescription': '未检测到有效的 Codex 登录凭据,无法开启 OAuth 配置模式。请先在 Codex 应用中登录 ChatGPT 账号,如果 Codex 应用无法触发登录,请先使用', 'agents.oauthLoginRequired.enableClearConfiguration': '清空配置', diff --git a/src/pages/AgentsPage.tsx b/src/pages/AgentsPage.tsx index f169daf7..c7e04d12 100644 --- a/src/pages/AgentsPage.tsx +++ b/src/pages/AgentsPage.tsx @@ -93,6 +93,7 @@ type AgentConfigStatus = { configurationSynchronized: boolean; currentModel: string | null; oauthConfiguration: boolean; + remoteCompaction: boolean; modificationEnabled: boolean; modificationState: AgentModificationState; backupAvailable: boolean; @@ -672,6 +673,7 @@ export function AgentsPage() { ); const [oauthLoginRequiredAction, setOauthLoginRequiredAction] = useState(null); const [oauthConfigurationDraft, setOauthConfigurationDraft] = useState(null); + const [remoteCompactionDraft, setRemoteCompactionDraft] = useState(null); const [piProviderUpdateStatus, setPiProviderUpdateStatus] = useState(null); const modelRequestRef = useRef(0); const piUpdateRequestRef = useRef(0); @@ -799,6 +801,7 @@ export function AgentsPage() { setLaunchDirectoryError(''); setOauthLoginRequiredAction(null); setOauthConfigurationDraft(null); + setRemoteCompactionDraft(null); // Preserve each Claude client's unsaved draft while navigating between clients. // Its dirty flag is cleared only after a successful apply, close, or reset action. }, [selected]); @@ -809,6 +812,9 @@ export function AgentsPage() { const oauthConfiguration = oauthConfigurationDraft ?? activeStatus?.oauthConfiguration ?? false; + const remoteCompaction = remoteCompactionDraft + ?? activeStatus?.remoteCompaction + ?? false; const savedSelectedModel = modelByClient[selected] ?? ''; const selectedModelOption = findAgentModel(models, savedSelectedModel); const selectedModel = selectedModelOption?.name ?? ''; @@ -934,7 +940,12 @@ export function AgentsPage() { ); const oauthConfigurationChanged = selected === 'codex' && oauthConfiguration !== Boolean(activeStatus?.oauthConfiguration); - const draftChanged = modelDraftChanged || claudeMappingDraftChanged || oauthConfigurationChanged; + const remoteCompactionChanged = selected === 'codex' + && remoteCompaction !== Boolean(activeStatus?.remoteCompaction); + const draftChanged = modelDraftChanged + || claudeMappingDraftChanged + || oauthConfigurationChanged + || remoteCompactionChanged; const configurationAction = resolveAgentConfigurationAction({ client: selected, modificationState: activeStatus?.modificationState ?? 'unconfigured', @@ -943,6 +954,8 @@ export function AgentsPage() { appliedModel, oauthConfiguration, appliedOauthConfiguration: Boolean(activeStatus?.oauthConfiguration), + remoteCompaction, + appliedRemoteCompaction: Boolean(activeStatus?.remoteCompaction), modelMappings: claudeModelMappingsDraft, appliedModelMappings: appliedClaudeModelMappings, }); @@ -1166,6 +1179,7 @@ export function AgentsPage() { client: selected, model, oauthConfiguration, + remoteCompaction, claudeCodeModelMappings: selected === 'claude-code' ? claudeModelMappings : null, claudeDesktopModelMappings: selected === 'claude-desktop' ? claudeModelMappings : null, }); @@ -1174,6 +1188,7 @@ export function AgentsPage() { } await reloadStatusesAfterAction(); setOauthConfigurationDraft(null); + setRemoteCompactionDraft(null); } catch (requestError) { if (!handleOAuthLoginError(requestError, 'apply')) { setConfigurationError(String(requestError)); @@ -1246,6 +1261,7 @@ export function AgentsPage() { const closeConfigurationChanges = async () => { const retainedOauthConfiguration = selected === 'codex' ? oauthConfiguration : null; + const retainedRemoteCompaction = selected === 'codex' ? remoteCompaction : null; setConfigurationError(''); setBusyAction('close-config'); try { @@ -1255,6 +1271,7 @@ export function AgentsPage() { } await reloadStatusesAfterAction(); setOauthConfigurationDraft(retainedOauthConfiguration); + setRemoteCompactionDraft(retainedRemoteCompaction); } catch (requestError) { setConfigurationError(String(requestError)); } finally { @@ -1276,6 +1293,7 @@ export function AgentsPage() { client: selected, model, oauthConfiguration, + remoteCompaction, claudeCodeModelMappings: selected === 'claude-code' ? claudeModelMappings : null, claudeDesktopModelMappings: selected === 'claude-desktop' ? claudeModelMappings : null, }); @@ -1285,6 +1303,7 @@ export function AgentsPage() { } await reloadStatusesAfterAction(); setOauthConfigurationDraft(null); + setRemoteCompactionDraft(null); } catch (requestError) { if (!handleOAuthLoginError(requestError, 'apply')) { setDefaultError(String(requestError)); @@ -1304,6 +1323,7 @@ export function AgentsPage() { setClearNotice(t('agents.clear.success')); await reloadStatusesAfterAction(); setOauthConfigurationDraft(null); + setRemoteCompactionDraft(null); } catch (requestError) { setClearError(String(requestError)); } finally { @@ -1799,7 +1819,7 @@ export function AgentsPage() { {selected === 'codex' ? (
+
) : null}
diff --git a/src/services/agentConfigurationDraft.ts b/src/services/agentConfigurationDraft.ts index 000330e2..8c38eb87 100644 --- a/src/services/agentConfigurationDraft.ts +++ b/src/services/agentConfigurationDraft.ts @@ -37,6 +37,8 @@ type ResolveAgentConfigurationActionOptions = { appliedModel: string; oauthConfiguration: boolean; appliedOauthConfiguration: boolean; + remoteCompaction: boolean; + appliedRemoteCompaction: boolean; modelMappings: AgentModelMappings; appliedModelMappings: AgentModelMappings; }; @@ -88,6 +90,8 @@ export function resolveAgentConfigurationAction({ appliedModel, oauthConfiguration, appliedOauthConfiguration, + remoteCompaction, + appliedRemoteCompaction, modelMappings, appliedModelMappings, }: ResolveAgentConfigurationActionOptions): AgentConfigurationAction { @@ -107,6 +111,8 @@ export function resolveAgentConfigurationAction({ ); const oauthChanged = client === 'codex' && oauthConfiguration !== appliedOauthConfiguration; + const remoteCompactionChanged = client === 'codex' + && remoteCompaction !== appliedRemoteCompaction; - return modelChanged || oauthChanged ? 'update' : 'close'; + return modelChanged || oauthChanged || remoteCompactionChanged ? 'update' : 'close'; } diff --git a/src/styles.css b/src/styles.css index b7e10fe6..f2372945 100644 --- a/src/styles.css +++ b/src/styles.css @@ -6388,14 +6388,14 @@ textarea:focus-visible { min-width: 0; } -.agent-codex-options > .agent-oauth-configuration { +.agent-codex-options > .agent-codex-option { height: 100%; min-height: 54px; padding: 10px 12px; } .agent-codex-options .agent-section-heading strong, -.agent-codex-options > .agent-oauth-configuration { +.agent-codex-options > .agent-codex-option { font-size: var(--font-size-meta); font-weight: 600; line-height: var(--line-height-tight); @@ -6411,7 +6411,7 @@ textarea:focus-visible { text-align: left; } -.agent-oauth-configuration { +.agent-codex-option { display: flex; min-height: 42px; align-items: center; @@ -6429,28 +6429,28 @@ textarea:focus-visible { transition: border-color 160ms ease, background-color 160ms ease, box-shadow 160ms ease; } -.agent-oauth-configuration:hover { +.agent-codex-option:hover { border-color: var(--theme-bdb5a9); background: var(--theme-f6f2ec); } -.agent-oauth-configuration:has(input:focus-visible) { +.agent-codex-option:has(input:focus-visible) { border-color: var(--theme-8d8377); box-shadow: 0 0 0 3px rgba(45, 42, 38, 0.1); } -.agent-oauth-configuration:has(input:checked) { +.agent-codex-option:has(input:checked) { border-color: var(--theme-9db8a5); background: var(--theme-f3f8f4); color: var(--theme-2f6b3f); } -.agent-oauth-configuration:has(input:disabled) { +.agent-codex-option:has(input:disabled) { cursor: not-allowed; opacity: 0.55; } -.agent-oauth-configuration .switch-control { +.agent-codex-option .switch-control { flex: none; }