use agent_core::reasoning::ReasoningLevel;
use serde_json::{json, Map, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KimiReasoningCapability {
Effort {
supported: &'static [ReasoningLevel],
default_level: Option<ReasoningLevel>,
can_disable: bool,
},
AlwaysThinking,
ToggleableThinking,
}
pub fn kimi_static_capability(model_id: &str) -> Option<KimiReasoningCapability> {
use ReasoningLevel::*;
match model_id {
"kimi-k3" => Some(KimiReasoningCapability::Effort {
supported: &[Low, High, Max],
default_level: Some(Max),
can_disable: false,
}),
"kimi-k2.7-code" | "kimi-k2.7-code-highspeed" => {
Some(KimiReasoningCapability::AlwaysThinking)
}
"kimi-k2.6" | "kimi-k2.5" => Some(KimiReasoningCapability::ToggleableThinking),
_ => None,
}
}
fn kimi_effort_label(level: ReasoningLevel) -> Option<&'static str> {
match level {
ReasoningLevel::Low => Some("low"),
ReasoningLevel::High => Some("high"),
ReasoningLevel::Max => Some("max"),
_ => None,
}
}
pub fn apply_kimi_reasoning_params(
body: &mut Map<String, Value>,
model_id: &str,
level: ReasoningLevel,
) {
match kimi_static_capability(model_id) {
Some(KimiReasoningCapability::Effort { supported, .. }) => {
if supported.contains(&level) {
if let Some(label) = kimi_effort_label(level) {
body.insert("reasoning_effort".to_string(), json!(label));
}
}
}
Some(KimiReasoningCapability::ToggleableThinking) => {
if level == ReasoningLevel::Off {
body.insert("thinking".to_string(), json!({ "type": "disabled" }));
}
}
Some(KimiReasoningCapability::AlwaysThinking) | None => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ReasoningLevel::*;
#[test]
fn k3_capability_is_exact_low_high_max_default_max_no_disable() {
let Some(KimiReasoningCapability::Effort {
supported,
default_level,
can_disable,
}) = kimi_static_capability("kimi-k3")
else {
panic!("kimi-k3 must have documented effort capability");
};
assert_eq!(supported, &[Low, High, Max]);
assert_eq!(default_level, Some(Max));
assert!(!can_disable);
}
#[test]
fn k27_code_family_is_always_thinking() {
for id in ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"] {
assert_eq!(
kimi_static_capability(id),
Some(KimiReasoningCapability::AlwaysThinking),
"{id}"
);
}
}
#[test]
fn k26_and_k25_are_toggleable() {
for id in ["kimi-k2.6", "kimi-k2.5"] {
assert_eq!(
kimi_static_capability(id),
Some(KimiReasoningCapability::ToggleableThinking),
"{id}"
);
}
}
#[test]
fn unknown_ids_fail_closed() {
assert_eq!(kimi_static_capability("kimi-k9"), None);
assert_eq!(kimi_static_capability(""), None);
assert_eq!(kimi_static_capability("kimi-k3-preview"), None);
}
#[test]
fn k3_body_carries_exact_reasoning_effort() {
for (level, label) in [(Low, "low"), (High, "high"), (Max, "max")] {
let mut body = Map::new();
apply_kimi_reasoning_params(&mut body, "kimi-k3", level);
assert_eq!(body.get("reasoning_effort"), Some(&json!(label)));
assert!(!body.contains_key("thinking"));
}
}
#[test]
fn k3_adaptive_omits_the_field() {
let mut body = Map::new();
apply_kimi_reasoning_params(&mut body, "kimi-k3", Adaptive);
assert!(body.is_empty());
}
#[test]
fn k3_unsupported_levels_are_omitted_never_guessed() {
for level in [Off, Medium, XHigh, Ultra, UltraCode] {
let mut body = Map::new();
apply_kimi_reasoning_params(&mut body, "kimi-k3", level);
assert!(body.is_empty(), "{level:?} must not serialize");
}
}
#[test]
fn k26_off_disables_thinking_and_other_levels_omit() {
let mut body = Map::new();
apply_kimi_reasoning_params(&mut body, "kimi-k2.6", Off);
assert_eq!(body.get("thinking"), Some(&json!({"type": "disabled"})));
assert!(!body.contains_key("reasoning_effort"));
for level in [Adaptive, Low, Medium, High, Max] {
let mut body = Map::new();
apply_kimi_reasoning_params(&mut body, "kimi-k2.6", level);
assert!(body.is_empty(), "{level:?} must omit all fields");
}
}
#[test]
fn always_thinking_and_unknown_send_nothing() {
for id in ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k9"] {
for level in [Off, Adaptive, Low, High, Max] {
let mut body = Map::new();
apply_kimi_reasoning_params(&mut body, id, level);
assert!(body.is_empty(), "{id} {level:?} must send nothing");
}
}
}
}