From 6acc3951193afa2e0f07d9b2541534bc8f2a848b Mon Sep 17 00:00:00 2001 From: LeoLin990405 Date: Fri, 31 Jul 2026 14:21:41 +0800 Subject: [PATCH 1/3] fix(catalog): honor Manager context window for suffixless custom models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit apply_model_catalog_to_config returned before generating a catalog when a custom model had neither a `[window]` suffix nor bundled metadata — even if the user configured a context window in the Manager. Codex then fell back to its bundled 272000 default, so the CLI showed ~258K regardless of the 1M the user set. Generate the catalog (and propagate the window) whenever the user supplied a context window too. Adds a regression test. Fixes #1594. --- crates/codex-plus-core/src/relay_config.rs | 49 ++++++++++++++++++---- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/crates/codex-plus-core/src/relay_config.rs b/crates/codex-plus-core/src/relay_config.rs index 63dbf72da..f5193dbea 100644 --- a/crates/codex-plus-core/src/relay_config.rs +++ b/crates/codex-plus-core/src/relay_config.rs @@ -1728,15 +1728,21 @@ fn apply_model_catalog_to_config( }; let entries = crate::model_suffix::collect_catalog_entries(&model_list, &model_windows, &profile.model); - // Known bundled metadata entries need a catalog even without a user-supplied window. - if !entries.iter().any(|entry| { - entry.suffix_window.is_some() - || crate::model_suffix::requires_bundled_metadata_catalog(&entry.slug) - || (official_deepseek_responses && entry.slug.starts_with("deepseek-v4-")) - }) { + let fallback = parse_optional_positive_u64(&profile.context_window, "上下文大小")?; + // Generate a catalog whenever there is something to customize: a per-model `[window]` + // suffix, bundled metadata, an official DeepSeek Responses model, OR a user-configured + // context window. Without the last case a suffixless custom model silently drops the + // Manager's context window, so Codex falls back to its bundled 272000 default and the CLI + // shows ~258K instead of the configured value (#1594). + if fallback.is_none() + && !entries.iter().any(|entry| { + entry.suffix_window.is_some() + || crate::model_suffix::requires_bundled_metadata_catalog(&entry.slug) + || (official_deepseek_responses && entry.slug.starts_with("deepseek-v4-")) + }) + { return Ok(config_text); } - let fallback = parse_optional_positive_u64(&profile.context_window, "上下文大小")?; let catalog_path = home.join(&catalog_relative); if let Some(parent) = catalog_path.parent() { std::fs::create_dir_all(parent)?; @@ -3175,6 +3181,35 @@ mod tests { }; assert!(relay_profile_model(&empty).trim().is_empty()); } + + // #1594: a custom model without a `[window]` suffix and without bundled metadata + // must still honor the context window the user configured in the Manager. Otherwise + // apply_model_catalog_to_config returns before generating a catalog, Codex falls back + // to its bundled 272000 default and the CLI shows ~258K instead of the configured 1M. + #[test] + fn generates_catalog_with_user_context_window_for_suffixless_custom_model() { + let temp = tempfile::tempdir().unwrap(); + let config = "model_provider = \"custom\"\nmodel = \"deepseek-v4-flash\"\n\n\ + [model_providers.custom]\nname = \"custom\"\nwire_api = \"responses\"\n\ + base_url = \"http://127.0.0.1:57321/v1\"\n"; + let profile = RelayProfile { + id: "ctxwin".to_string(), + model: "deepseek-v4-flash".to_string(), + model_list: "deepseek-v4-flash".to_string(), // no [1m] suffix + context_window: "1000000".to_string(), // user configured 1M in the Manager + ..RelayProfile::default() + }; + + let result = apply_model_catalog_to_config(temp.path(), &profile, config).unwrap(); + + let catalog_rel = root_key_string(&result, "model_catalog_json") + .expect("a catalog should be generated when the user set a context window"); + let catalog = std::fs::read_to_string(temp.path().join(catalog_rel)).unwrap(); + assert!( + catalog.contains("1000000"), + "catalog must carry the configured context window, not codex's default; got: {catalog}" + ); + } } pub fn root_key_string(contents: &str, key: &str) -> Option { From d367bd15565358c2ec81a0f771946d76fc53cd0c Mon Sep 17 00:00:00 2001 From: LeoLin990405 Date: Tue, 11 Aug 2026 21:09:28 +0800 Subject: [PATCH 2/3] test(relay_config): fix two integration tests broken by the catalog fix apply_relay_profile_{does_not_write_model_catalog_json_for_selected_models, no_catalog_when_model_list_has_no_suffix} set a context window and asserted no catalog. Now that a configured window generates a catalog (#1594), drop the window from these no-suffix cases so they keep exercising the no-catalog path; window->config.toml stays covered elsewhere and window->catalog is covered by generates_catalog_with_user_context_window_*. --- crates/codex-plus-core/tests/relay_config.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/codex-plus-core/tests/relay_config.rs b/crates/codex-plus-core/tests/relay_config.rs index fd6738ee6..88feda273 100644 --- a/crates/codex-plus-core/tests/relay_config.rs +++ b/crates/codex-plus-core/tests/relay_config.rs @@ -1716,17 +1716,16 @@ experimental_bearer_token = "sk-new" auth_contents: r#"{"OPENAI_API_KEY":"sk-new"}"#.to_string(), model_insert_mode: Default::default(), model_list: "deepseek-coder\nqwen3-coder".to_string(), - context_window: "200000".to_string(), - auto_compact_limit: "160000".to_string(), ..RelayProfile::default() }; apply_relay_profile_files_to_home_with_context(temp.path(), &profile, "").unwrap(); let config = std::fs::read_to_string(temp.path().join("config.toml")).unwrap(); + // No `[window]` suffix, no bundled metadata, and no configured context window -> + // still no catalog. (A configured window now generates one; see the dedicated + // generates_catalog_with_user_context_window_* test and #1594/#1722.) assert!(!config.contains("model_catalog_json")); - assert!(config.contains("model_context_window = 200000")); - assert!(config.contains("model_auto_compact_token_limit = 160000")); assert!(!temp.path().join("model-catalogs").exists()); } @@ -3940,16 +3939,15 @@ experimental_bearer_token = "sk-new" auth_contents: r#"{"OPENAI_API_KEY":"sk-new"}"#.to_string(), model_insert_mode: Default::default(), model_list: "deepseek-coder\nqwen3-coder".to_string(), - context_window: "200000".to_string(), - auto_compact_limit: "160000".to_string(), ..RelayProfile::default() }; apply_relay_profile_files_to_home_with_context(temp.path(), &profile, "").unwrap(); let config = std::fs::read_to_string(temp.path().join("config.toml")).unwrap(); + // No `[window]` suffix, no bundled metadata, and no configured context window -> + // still no catalog. (A configured window now generates one; see #1594/#1722.) assert!(!config.contains("model_catalog_json")); - assert!(config.contains("model_context_window = 200000")); assert!(!temp.path().join("model-catalogs").exists()); } From 63f58cf6259a6cf2c1a2eebc6bac22ba1a6b4ba8 Mon Sep 17 00:00:00 2001 From: LeoLin990405 Date: Thu, 27 Aug 2026 14:45:28 +0800 Subject: [PATCH 3/3] test(relay_config): assert unconfigured window keeps old no-catalog behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: complement generates_catalog_with_user_context_window_* with a regression test proving a suffixless, non-bundled, non-DeepSeek-v4 custom model generates NO catalog when no context window is configured — i.e. the fix only adds catalog generation for the explicitly-configured-window case. --- crates/codex-plus-core/src/relay_config.rs | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/codex-plus-core/src/relay_config.rs b/crates/codex-plus-core/src/relay_config.rs index f5193dbea..321b3d675 100644 --- a/crates/codex-plus-core/src/relay_config.rs +++ b/crates/codex-plus-core/src/relay_config.rs @@ -3210,6 +3210,33 @@ mod tests { "catalog must carry the configured context window, not codex's default; got: {catalog}" ); } + + // #1594 (complement): with NO context window configured, a suffixless custom model that + // isn't bundled metadata (and isn't an official DeepSeek Responses model) keeps the old + // behavior — no catalog is generated. Guards the fix against widening catalog generation + // beyond the explicitly-configured-window case. + #[test] + fn no_catalog_for_suffixless_custom_model_without_context_window() { + let temp = tempfile::tempdir().unwrap(); + let config = "model_provider = \"custom\"\nmodel = \"qwen3-coder\"\n\n\ + [model_providers.custom]\nname = \"custom\"\nwire_api = \"responses\"\n\ + base_url = \"http://127.0.0.1:57321/v1\"\n"; + let profile = RelayProfile { + id: "noctx".to_string(), + model: "qwen3-coder".to_string(), + model_list: "qwen3-coder".to_string(), // no [window] suffix, not bundled, not deepseek-v4 + // context_window intentionally left empty -> old behavior must be preserved. + ..RelayProfile::default() + }; + + let result = apply_model_catalog_to_config(temp.path(), &profile, config).unwrap(); + + assert!( + root_key_string(&result, "model_catalog_json").is_none(), + "no configured context window must not generate a catalog (old behavior preserved)" + ); + assert!(!temp.path().join("model-catalogs").exists()); + } } pub fn root_key_string(contents: &str, key: &str) -> Option {