Skip to content
Open
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
42 changes: 42 additions & 0 deletions codex-rs/app-server/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,48 @@ mod tests {
assert_eq!(models[8].model, "nvidia/nemotron-3-ultra-550b-a55b");
}

#[test]
fn zai_fallback_models_expose_glm_5_2_reasoning_efforts() {
let models = fallback_supported_models_for_provider("zai", /*include_hidden*/ false);

assert_eq!(models[0].model, "glm-5.2");
assert_eq!(models[0].display_name, "GLM-5.2");
assert!(models[0].is_default);
assert!(!models[0].hidden);
// Direct Z.ai docs default GLM-5.2 to `reasoning_effort = max`, represented via
// `Custom("max")` since the enum has no first-class `Max` variant.
assert_eq!(
models[0].default_reasoning_effort,
ReasoningEffort::Custom("max".to_string())
);
assert_eq!(
models[0]
.supported_reasoning_efforts
.iter()
.map(|effort| effort.reasoning_effort.clone())
.collect::<Vec<_>>(),
vec![
ReasoningEffort::None,
ReasoningEffort::Minimal,
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
ReasoningEffort::XHigh,
ReasoningEffort::Custom("max".to_string()),
]
);

// Other GLM fallbacks use Z.ai's provider-specific `thinking` parameter rather than
// OpenAI-compatible `reasoning_effort`, so the fallback list should not advertise
// selectable reasoning efforts for them.
let glm_5_1 = models
.iter()
.find(|model| model.model == "glm-5.1")
.expect("glm-5.1 fallback should exist");
assert_eq!(glm_5_1.default_reasoning_effort, ReasoningEffort::None);
assert_eq!(glm_5_1.supported_reasoning_efforts, Vec::new());
}

#[test]
fn xiaomi_fallback_models_include_current_default_and_legacy_alias() {
let models =
Expand Down
30 changes: 30 additions & 0 deletions codex-rs/app-server/tests/suite/v2/model_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,36 @@ wire_api = "responses"
);
assert!(data.iter().any(|model| model.is_default));
assert!(next_cursor.is_none());

if provider_id == "zai" {
// Direct Z.ai GLM-5.2 defaults to `reasoning_effort = max` (represented via
// `Custom("max")`) and exposes the full granular scale through fallback listing.
let glm_5_2 = data
.first()
.expect("zai fallback listing should include glm-5.2");
assert_eq!(
glm_5_2.default_reasoning_effort,
"max".parse().map_err(Error::msg)?,
"zai glm-5.2 should default to max reasoning effort"
);
assert_eq!(
glm_5_2
.supported_reasoning_efforts
.iter()
.map(|effort| effort.reasoning_effort.clone())
.collect::<Vec<_>>(),
vec![
"none".parse().map_err(Error::msg)?,
"minimal".parse().map_err(Error::msg)?,
"low".parse().map_err(Error::msg)?,
"medium".parse().map_err(Error::msg)?,
"high".parse().map_err(Error::msg)?,
"xhigh".parse().map_err(Error::msg)?,
"max".parse().map_err(Error::msg)?,
],
"zai glm-5.2 should expose the corrected reasoning-effort scale"
);
}
}
Ok(())
}
Expand Down
64 changes: 53 additions & 11 deletions codex-rs/known-provider-models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -869,26 +869,68 @@ mod tests {
)
)
);
}

#[test]
fn zai_glm_5_2_exposes_source_backed_reasoning_scale() {
// Direct Z.ai docs show GLM-5.2 defaults to `reasoning_effort = max` and supports the full
// granular scale, unlike the other GLM models which use Z.ai's provider-specific
// `thinking` parameter instead of OpenAI-compatible reasoning effort.
let (default_effort, presets) =
reasoning_levels_for_local_fallback(Some(ZAI_PROVIDER_ID), "glm-5.2");

assert_eq!(
(default_effort, presets),
(
Some(ReasoningEffort::Custom("max".to_string())),
vec![
reasoning_preset(ReasoningEffort::None, "Reasoning disabled"),
reasoning_preset(ReasoningEffort::High, "High reasoning"),
reasoning_preset(ReasoningEffort::Custom("max".to_string()), "Max reasoning"),
],
)
default_effort,
Some(ReasoningEffort::Custom("max".to_string()))
);
assert_eq!(
reasoning_levels_for_local_fallback(Some(ZAI_PROVIDER_ID), "glm-5.1"),
(None, Vec::new())
presets
.iter()
.map(|preset| preset.effort.clone())
.collect::<Vec<_>>(),
vec![
ReasoningEffort::None,
ReasoningEffort::Minimal,
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
ReasoningEffort::XHigh,
ReasoningEffort::Custom("max".to_string()),
]
);

// The 1M-context variant shares the same direct metadata.
assert_eq!(
reasoning_levels_for_local_fallback(Some(ZAI_PROVIDER_ID), "glm-5.2[1m]"),
reasoning_levels_for_local_fallback(Some(ZAI_PROVIDER_ID), "glm-5.2")
);
}

#[test]
fn zai_non_glm_5_2_models_do_not_expose_reasoning_effort() {
for slug in [
"glm-5.1",
"glm-5",
"glm-5-turbo",
"glm-4.7",
"glm-4.7-flashx",
"glm-4.7-flash",
] {
let (default_effort, presets) =
reasoning_levels_for_local_fallback(Some(ZAI_PROVIDER_ID), slug);

assert_eq!(
default_effort, None,
"{slug} should not expose an OpenAI-compatible reasoning default"
);
assert_eq!(
presets,
Vec::new(),
"{slug} should not expose OpenAI-compatible reasoning presets"
);
}
}

#[test]
fn provider_for_fallback_model_finds_unique_configured_provider() {
assert_eq!(
Expand Down
22 changes: 18 additions & 4 deletions codex-rs/known-provider-models/src/zai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,31 @@ pub(crate) fn reasoning_levels(
slug: &str,
) -> (Option<ReasoningEffort>, Vec<ReasoningEffortPreset>) {
match slug {
// Direct Z.ai docs (https://docs.z.ai/guides/llm/glm-5.2 and
// https://docs.z.ai/api-reference/llm/chat-completion, accessed 2026-07-09) show that only
// GLM-5.2 exposes the granular `reasoning_effort` scale (max, xhigh, high, medium, low,
// minimal, none) with a default of `max`. Other GLM models use Z.ai's provider-specific
// `thinking` toggle, so Codewith should not expose those as OpenAI-compatible
// `reasoning_effort` options.
//
// Codewith's `ReasoningEffort` enum has no first-class `max` variant, so `max` is
// represented via `Custom("max")`, which serializes to the exact wire value `max`. If a
// first-class `Max` variant is ever needed, that is a separate protocol follow-up.
"glm-5.2" | "glm-5.2[1m]" => (
Some(ReasoningEffort::Custom("max".to_string())),
vec![
reasoning_preset(ReasoningEffort::None, "Reasoning disabled"),
reasoning_preset(ReasoningEffort::Minimal, "Minimal reasoning"),
reasoning_preset(ReasoningEffort::Low, "Low reasoning"),
reasoning_preset(ReasoningEffort::Medium, "Moderate reasoning"),
reasoning_preset(ReasoningEffort::High, "High reasoning"),
reasoning_preset(ReasoningEffort::Custom("max".to_string()), "Max reasoning"),
reasoning_preset(ReasoningEffort::XHigh, "Extended reasoning"),
reasoning_preset(
ReasoningEffort::Custom("max".to_string()),
"Maximum reasoning",
),
],
),
"glm-5.1" | "glm-5" | "glm-5-turbo" | "glm-4.7" | "glm-4.7-flashx" | "glm-4.7-flash" => {
(None, Vec::new())
}
_ => (None, Vec::new()),
}
}
Expand Down
18 changes: 17 additions & 1 deletion codex-rs/models-manager/src/model_info_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,13 +382,29 @@ fn known_zai_model_uses_local_metadata_with_reasoning_and_search_support() {
effort: ReasoningEffort::None,
description: "Reasoning disabled".to_string(),
},
ReasoningEffortPreset {
effort: ReasoningEffort::Minimal,
description: "Minimal reasoning".to_string(),
},
ReasoningEffortPreset {
effort: ReasoningEffort::Low,
description: "Low reasoning".to_string(),
},
ReasoningEffortPreset {
effort: ReasoningEffort::Medium,
description: "Moderate reasoning".to_string(),
},
ReasoningEffortPreset {
effort: ReasoningEffort::High,
description: "High reasoning".to_string(),
},
ReasoningEffortPreset {
effort: ReasoningEffort::XHigh,
description: "Extended reasoning".to_string(),
},
ReasoningEffortPreset {
effort: ReasoningEffort::Custom("max".to_string()),
description: "Max reasoning".to_string(),
description: "Maximum reasoning".to_string(),
},
]
);
Expand Down
Loading