diff --git a/.gitattributes b/.gitattributes index 561487b9d..a9c87e9b9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,7 +10,14 @@ # Preserve the original Snow Skin asset line endings for byte-exact bundling. assets/inject/upstream/snow-skin/*.js text eol=lf assets/inject/upstream/snow-skin/*.css text eol=lf +assets/inject/upstream/glass-vision/*.js text eol=lf +assets/inject/upstream/glass-vision/*.css text eol=lf # Keep byte-exact macOS theme assets identical on every checkout platform. assets/inject/upstream/*/macos/*.js text eol=lf assets/inject/upstream/*/macos/*.css text eol=lf + +# Keep byte-exact Windows theme assets and skin manifests stable on checkout. +assets/inject/upstream/*/windows/*.js text eol=lf +assets/inject/upstream/*/windows/*.css text eol=lf +assets/inject/upstream/skin-packs/packs/*/theme.json text eol=lf diff --git a/crates/codex-plus-core/src/relay_config.rs b/crates/codex-plus-core/src/relay_config.rs index 77409d0b1..494ff6d2a 100644 --- a/crates/codex-plus-core/src/relay_config.rs +++ b/crates/codex-plus-core/src/relay_config.rs @@ -1593,14 +1593,20 @@ 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) - }) { + 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, 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) + }) + { return Ok(config_text.to_string()); } - 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)?; @@ -2734,6 +2740,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 { diff --git a/crates/codex-plus-core/tests/relay_config.rs b/crates/codex-plus-core/tests/relay_config.rs index 22c9194b3..911675998 100644 --- a/crates/codex-plus-core/tests/relay_config.rs +++ b/crates/codex-plus-core/tests/relay_config.rs @@ -1275,17 +1275,14 @@ 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 suffix, no bundled metadata, no context window -> no catalog. 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()); } @@ -3322,16 +3319,14 @@ 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 suffix, no bundled metadata, no context window -> no catalog. assert!(!config.contains("model_catalog_json")); - assert!(config.contains("model_context_window = 200000")); assert!(!temp.path().join("model-catalogs").exists()); }