Wire K3 thinking-effort levels end to end (fixes 'model does not support reasoning effort')
The live /models wire (verified against api.kimi.com) marks every Kimi
Code model supports_thinking_type: "only" and gives K3 a think_efforts
block {support, valid_efforts: [low, high, max], default_effort: max} —
both of which the F4 sync discarded, hardcoding
supports_reasoning_effort: false. Every effort selection was therefore
rejected with 'current model does not support reasoning effort'.
- kigi-models: WireModel gains supports_thinking_type + WireThinkEfforts;
"only" forces the always_thinking capability.
- models_fetch: think_efforts maps into the catalog entry — wire tokens
stay the option ids/labels (max/Max), canonical values map via the
ReasoningEffort parser (max → Xhigh), default_effort marks the default.
This lights up the existing /model <model> [effort] two-phase completion
and the /effort menu with the server's own vocabulary.
- kimi_compat: the effort level rides the wire as thinking.effort
({"type": "enabled", "effort": "low"} is accepted live; invalid
levels are a 400). Only the canonical-vs-wire spelling divergence
(xhigh → max) is renamed; levels pass through verbatim so a contract
violation surfaces instead of being clamped away.
Live acceptance: kigi -m kimi-code/k3 --reasoning-effort max -p ... round
trips against api.kimi.com, and the refreshed models_cache.json carries
low/high/max with the max default.
This commit is contained in:
@@ -187,6 +187,28 @@ pub struct WireModel {
|
|||||||
pub supports_video_in: bool,
|
pub supports_video_in: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
|
/// `"only"` marks always-thinking models (thinking cannot be disabled).
|
||||||
|
/// Verified against the live `api.kimi.com/coding/v1/models` response.
|
||||||
|
#[serde(default)]
|
||||||
|
pub supports_thinking_type: Option<String>,
|
||||||
|
/// Selectable thinking-effort levels, present only on models that offer
|
||||||
|
/// them (e.g. K3). Verified against the live `/models` response.
|
||||||
|
#[serde(default)]
|
||||||
|
pub think_efforts: Option<WireThinkEfforts>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `think_efforts` object of a `/models` entry. Live wire shape
|
||||||
|
/// (api.kimi.com, 2026-07):
|
||||||
|
/// `{"support": true, "valid_efforts": ["low", "high", "max"],
|
||||||
|
/// "default_effort": "max"}`.
|
||||||
|
#[derive(Debug, Clone, Default, serde::Deserialize)]
|
||||||
|
pub struct WireThinkEfforts {
|
||||||
|
#[serde(default)]
|
||||||
|
pub support: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub valid_efforts: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub default_effort: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `GET {base}/models` response envelope.
|
/// `GET {base}/models` response envelope.
|
||||||
@@ -203,14 +225,26 @@ impl WireModel {
|
|||||||
/// - `supports_image_in` → image_in; `supports_video_in` → video_in
|
/// - `supports_image_in` → image_in; `supports_video_in` → video_in
|
||||||
/// - id starts with `kimi-k2` → thinking + image_in + video_in
|
/// - id starts with `kimi-k2` → thinking + image_in + video_in
|
||||||
///
|
///
|
||||||
|
/// On top of that, the live wire's `supports_thinking_type: "only"`
|
||||||
|
/// marks a model whose thinking cannot be disabled → always_thinking.
|
||||||
|
///
|
||||||
/// Returned sorted + deduplicated ([`ModelCapability`]'s `Ord`).
|
/// Returned sorted + deduplicated ([`ModelCapability`]'s `Ord`).
|
||||||
pub fn capabilities(&self) -> Vec<ModelCapability> {
|
pub fn capabilities(&self) -> Vec<ModelCapability> {
|
||||||
derive_capabilities(
|
let mut caps = derive_capabilities(
|
||||||
&self.id,
|
&self.id,
|
||||||
self.supports_reasoning,
|
self.supports_reasoning,
|
||||||
self.supports_image_in,
|
self.supports_image_in,
|
||||||
self.supports_video_in,
|
self.supports_video_in,
|
||||||
)
|
);
|
||||||
|
if self.supports_thinking_type.as_deref() == Some("only") {
|
||||||
|
for cap in [ModelCapability::Thinking, ModelCapability::AlwaysThinking] {
|
||||||
|
if !caps.contains(&cap) {
|
||||||
|
caps.push(cap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
caps.sort();
|
||||||
|
}
|
||||||
|
caps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,6 +386,63 @@ pub fn default_session_summary_model() -> &'static str {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
/// Mirror of the live `api.kimi.com/coding/v1/models` K3 entry
|
||||||
|
/// (fetched 2026-07-17): `supports_thinking_type: "only"` plus a
|
||||||
|
/// `think_efforts` block with low/high/max and a max default.
|
||||||
|
#[test]
|
||||||
|
fn wire_model_parses_live_k3_think_efforts() {
|
||||||
|
let json = serde_json::json!({
|
||||||
|
"id": "k3",
|
||||||
|
"created": 1_761_264_000,
|
||||||
|
"object": "model",
|
||||||
|
"display_name": "K3",
|
||||||
|
"type": "model",
|
||||||
|
"context_length": 1_048_576,
|
||||||
|
"supports_reasoning": true,
|
||||||
|
"supports_image_in": true,
|
||||||
|
"supports_video_in": true,
|
||||||
|
"supports_thinking_type": "only",
|
||||||
|
"think_efforts": {
|
||||||
|
"support": true,
|
||||||
|
"valid_efforts": ["low", "high", "max"],
|
||||||
|
"default_effort": "max"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let wire: WireModel = serde_json::from_value(json).unwrap();
|
||||||
|
let efforts = wire.think_efforts.as_ref().unwrap();
|
||||||
|
assert!(efforts.support);
|
||||||
|
assert_eq!(efforts.valid_efforts, ["low", "high", "max"]);
|
||||||
|
assert_eq!(efforts.default_effort.as_deref(), Some("max"));
|
||||||
|
// "only" thinking type forces always_thinking on top of the
|
||||||
|
// supports_reasoning-derived thinking capability.
|
||||||
|
let caps = wire.capabilities();
|
||||||
|
assert!(caps.contains(&ModelCapability::Thinking));
|
||||||
|
assert!(caps.contains(&ModelCapability::AlwaysThinking));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The K2.7 entries carry `supports_thinking_type: "only"` but no
|
||||||
|
/// `think_efforts` — always-thinking without selectable levels.
|
||||||
|
#[test]
|
||||||
|
fn wire_model_without_think_efforts_still_always_thinking() {
|
||||||
|
let json = serde_json::json!({
|
||||||
|
"id": "kimi-for-coding",
|
||||||
|
"context_length": 262_144,
|
||||||
|
"supports_reasoning": true,
|
||||||
|
"supports_image_in": true,
|
||||||
|
"supports_video_in": true,
|
||||||
|
"supports_thinking_type": "only"
|
||||||
|
});
|
||||||
|
let wire: WireModel = serde_json::from_value(json).unwrap();
|
||||||
|
assert!(wire.think_efforts.is_none());
|
||||||
|
let caps = wire.capabilities();
|
||||||
|
assert!(caps.contains(&ModelCapability::AlwaysThinking));
|
||||||
|
// Sorted + deduplicated invariant holds after the "only" injection.
|
||||||
|
let mut sorted = caps.clone();
|
||||||
|
sorted.sort();
|
||||||
|
sorted.dedup();
|
||||||
|
assert_eq!(caps, sorted);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn platform_ids_round_trip() {
|
fn platform_ids_round_trip() {
|
||||||
for p in PlatformId::ALL {
|
for p in PlatformId::ALL {
|
||||||
@@ -463,6 +554,8 @@ mod tests {
|
|||||||
supports_image_in: false,
|
supports_image_in: false,
|
||||||
supports_video_in: false,
|
supports_video_in: false,
|
||||||
display_name: None,
|
display_name: None,
|
||||||
|
supports_thinking_type: None,
|
||||||
|
think_efforts: None,
|
||||||
},
|
},
|
||||||
WireModel {
|
WireModel {
|
||||||
id: "moonshot-v1-8k".into(),
|
id: "moonshot-v1-8k".into(),
|
||||||
@@ -471,6 +564,8 @@ mod tests {
|
|||||||
supports_image_in: false,
|
supports_image_in: false,
|
||||||
supports_video_in: false,
|
supports_video_in: false,
|
||||||
display_name: None,
|
display_name: None,
|
||||||
|
supports_thinking_type: None,
|
||||||
|
think_efforts: None,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let filtered = filter_allowed_models(PlatformId::MoonshotCn, listing.clone());
|
let filtered = filter_allowed_models(PlatformId::MoonshotCn, listing.clone());
|
||||||
|
|||||||
@@ -32,12 +32,21 @@ pub(crate) fn adapt_chat_completions_body(body: &mut Value) {
|
|||||||
/// Map the OpenAI-style `reasoning_effort` knob onto Kimi's `thinking`
|
/// Map the OpenAI-style `reasoning_effort` knob onto Kimi's `thinking`
|
||||||
/// request field and drop `reasoning_effort` from the wire.
|
/// request field and drop `reasoning_effort` from the wire.
|
||||||
///
|
///
|
||||||
/// kimi-cli controls thinking exclusively through the request body's
|
/// kimi-cli 1.49.0 controls thinking through the request body's
|
||||||
/// `thinking: {"type": "enabled" | "disabled"}` field
|
/// `thinking: {"type": "enabled" | "disabled"}` field
|
||||||
/// (packages/kosong/src/kosong/chat_provider/kimi.py:214-223 `with_thinking`:
|
/// (packages/kosong/src/kosong/chat_provider/kimi.py:214-223 `with_thinking`:
|
||||||
/// `"enabled" if effort != "off" else "disabled"`; wired by
|
/// `"enabled" if effort != "off" else "disabled"`; wired by
|
||||||
/// src/kimi_cli/llm.py:475-481). When no effort is configured, nothing is
|
/// src/kimi_cli/llm.py:475-481). When no effort is configured, nothing is
|
||||||
/// sent and the server default applies (llm.py:482 "leave as-is").
|
/// sent and the server default applies (llm.py:482 "leave as-is").
|
||||||
|
///
|
||||||
|
/// Models with selectable levels (the `/models` `think_efforts` block, e.g.
|
||||||
|
/// K3's low/high/max) additionally take the level as `thinking.effort` —
|
||||||
|
/// verified against the live api.kimi.com: `{"type": "enabled", "effort":
|
||||||
|
/// "low"}` is accepted, values outside `valid_efforts` are a 400. The
|
||||||
|
/// catalog gates efforts to that per-model list, so this layer only renames
|
||||||
|
/// the one canonical-vs-wire divergence (`xhigh` → `max`) and passes the
|
||||||
|
/// level through verbatim — inventing or clamping a level here would hide a
|
||||||
|
/// real contract violation.
|
||||||
fn adapt_thinking(body: &mut Value) {
|
fn adapt_thinking(body: &mut Value) {
|
||||||
let Some(obj) = body.as_object_mut() else {
|
let Some(obj) = body.as_object_mut() else {
|
||||||
return;
|
return;
|
||||||
@@ -45,11 +54,22 @@ fn adapt_thinking(body: &mut Value) {
|
|||||||
let Some(effort) = obj.remove("reasoning_effort") else {
|
let Some(effort) = obj.remove("reasoning_effort") else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let enabled = effort.as_str() != Some("none");
|
let effort = effort.as_str().map(str::to_owned);
|
||||||
obj.insert(
|
let enabled = effort.as_deref() != Some("none");
|
||||||
"thinking".to_owned(),
|
let mut thinking = serde_json::Map::new();
|
||||||
serde_json::json!({ "type": if enabled { "enabled" } else { "disabled" } }),
|
thinking.insert(
|
||||||
|
"type".to_owned(),
|
||||||
|
Value::String(if enabled { "enabled" } else { "disabled" }.to_owned()),
|
||||||
);
|
);
|
||||||
|
if enabled && let Some(level) = effort {
|
||||||
|
let wire_level = if level == "xhigh" {
|
||||||
|
"max".to_owned()
|
||||||
|
} else {
|
||||||
|
level
|
||||||
|
};
|
||||||
|
thinking.insert("effort".to_owned(), Value::String(wire_level));
|
||||||
|
}
|
||||||
|
obj.insert("thinking".to_owned(), Value::Object(thinking));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Message-level adaptations:
|
/// Message-level adaptations:
|
||||||
@@ -262,12 +282,27 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reasoning_effort_maps_to_kimi_thinking_field() {
|
fn reasoning_effort_maps_to_kimi_thinking_field() {
|
||||||
|
// Level rides along as thinking.effort (live wire: 200 with
|
||||||
|
// {"type": "enabled", "effort": "low"}).
|
||||||
let mut body = json!({ "model": "kimi-for-coding", "reasoning_effort": "high" });
|
let mut body = json!({ "model": "kimi-for-coding", "reasoning_effort": "high" });
|
||||||
adapt_chat_completions_body(&mut body);
|
adapt_chat_completions_body(&mut body);
|
||||||
assert_eq!(body.get("reasoning_effort"), None);
|
assert_eq!(body.get("reasoning_effort"), None);
|
||||||
assert_eq!(body["thinking"], json!({ "type": "enabled" }));
|
assert_eq!(
|
||||||
|
body["thinking"],
|
||||||
|
json!({ "type": "enabled", "effort": "high" })
|
||||||
|
);
|
||||||
|
|
||||||
// kimi.py:218: "off" (our ReasoningEffort::None) → disabled.
|
// Canonical `xhigh` is spelled `max` on the Kimi wire (the K3
|
||||||
|
// valid_efforts vocabulary is low/high/max).
|
||||||
|
let mut body = json!({ "model": "k3", "reasoning_effort": "xhigh" });
|
||||||
|
adapt_chat_completions_body(&mut body);
|
||||||
|
assert_eq!(
|
||||||
|
body["thinking"],
|
||||||
|
json!({ "type": "enabled", "effort": "max" })
|
||||||
|
);
|
||||||
|
|
||||||
|
// kimi.py:218: "off" (our ReasoningEffort::None) → disabled, and no
|
||||||
|
// effort key (a disabled+effort combination would be contradictory).
|
||||||
let mut body = json!({ "reasoning_effort": "none" });
|
let mut body = json!({ "reasoning_effort": "none" });
|
||||||
adapt_chat_completions_body(&mut body);
|
adapt_chat_completions_body(&mut body);
|
||||||
assert_eq!(body["thinking"], json!({ "type": "disabled" }));
|
assert_eq!(body["thinking"], json!({ "type": "disabled" }));
|
||||||
|
|||||||
@@ -382,9 +382,12 @@ async fn request_carries_bearer_kigi_ua_and_kimi_dialect_body() {
|
|||||||
assert_eq!(body["stream"], json!(true));
|
assert_eq!(body["stream"], json!(true));
|
||||||
assert_eq!(body["stream_options"], json!({ "include_usage": true }));
|
assert_eq!(body["stream_options"], json!({ "include_usage": true }));
|
||||||
|
|
||||||
// -- Thinking mapping (kimi.py:214-223): effort → thinking, no
|
// -- Thinking mapping (kimi.py:214-223 + live think_efforts wire):
|
||||||
// reasoning_effort on the wire.
|
// effort → thinking {type, effort}, no reasoning_effort on the wire.
|
||||||
assert_eq!(body["thinking"], json!({ "type": "enabled" }));
|
assert_eq!(
|
||||||
|
body["thinking"],
|
||||||
|
json!({ "type": "enabled", "effort": "high" })
|
||||||
|
);
|
||||||
assert_eq!(body.get("reasoning_effort"), None);
|
assert_eq!(body.get("reasoning_effort"), None);
|
||||||
|
|
||||||
// -- Message adaptations.
|
// -- Message adaptations.
|
||||||
|
|||||||
@@ -303,12 +303,49 @@ fn fetch_one_platform_models(
|
|||||||
/// platforms — never key values — because raw fetched entries are persisted
|
/// platforms — never key values — because raw fetched entries are persisted
|
||||||
/// to the models disk cache. Config-file keys are stamped in-memory later by
|
/// to the models disk cache. Config-file keys are stamped in-memory later by
|
||||||
/// `resolve_model_list`'s platform-credentials layer.
|
/// `resolve_model_list`'s platform-credentials layer.
|
||||||
|
/// Map a live `think_efforts` block to catalog effort options. The wire
|
||||||
|
/// token stays the option id/label (`"max"` → label `"Max"`) so the UI
|
||||||
|
/// mirrors the server's vocabulary, while the canonical value maps through
|
||||||
|
/// the [`kigi_sampling_types::ReasoningEffort`] parser (`"max"` → `Xhigh`).
|
||||||
|
/// Unknown tokens are dropped with a warning rather than inventing a level.
|
||||||
|
fn think_efforts_to_options(
|
||||||
|
think: &kigi_models::WireThinkEfforts,
|
||||||
|
) -> Vec<kigi_sampling_types::ReasoningEffortOption> {
|
||||||
|
think
|
||||||
|
.valid_efforts
|
||||||
|
.iter()
|
||||||
|
.filter_map(|token| {
|
||||||
|
let value = match token.parse::<kigi_sampling_types::ReasoningEffort>() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(error) => {
|
||||||
|
tracing::warn!(%token, %error, "unknown think_efforts token; dropping");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut label: String = token.clone();
|
||||||
|
if let Some(first) = label.get_mut(0..1) {
|
||||||
|
first.make_ascii_uppercase();
|
||||||
|
}
|
||||||
|
Some(kigi_sampling_types::ReasoningEffortOption {
|
||||||
|
id: token.clone(),
|
||||||
|
value,
|
||||||
|
label,
|
||||||
|
description: None,
|
||||||
|
default: think.default_effort.as_deref() == Some(token.as_str()),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
fn platform_wire_model_to_entry(
|
fn platform_wire_model_to_entry(
|
||||||
platform: kigi_models::PlatformId,
|
platform: kigi_models::PlatformId,
|
||||||
wire: kigi_models::WireModel,
|
wire: kigi_models::WireModel,
|
||||||
base_url: &str,
|
base_url: &str,
|
||||||
) -> crate::agent::config::ModelEntryConfig {
|
) -> crate::agent::config::ModelEntryConfig {
|
||||||
let capabilities = wire.capabilities();
|
let capabilities = wire.capabilities();
|
||||||
|
// Selectable thinking levels (live wire `think_efforts`, e.g. K3's
|
||||||
|
// low/high/max). `support: false` or absence both mean "no levels".
|
||||||
|
let think_efforts = wire.think_efforts.as_ref().filter(|t| t.support);
|
||||||
let context_window = std::num::NonZeroU64::new(wire.context_length).unwrap_or_else(|| {
|
let context_window = std::num::NonZeroU64::new(wire.context_length).unwrap_or_else(|| {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
model = %wire.id,
|
model = %wire.id,
|
||||||
@@ -332,9 +369,13 @@ fn platform_wire_model_to_entry(
|
|||||||
env_key,
|
env_key,
|
||||||
api_backend: Default::default(),
|
api_backend: Default::default(),
|
||||||
auth_scheme: None,
|
auth_scheme: None,
|
||||||
reasoning_effort: None,
|
reasoning_effort: think_efforts
|
||||||
supports_reasoning_effort: false,
|
.and_then(|t| t.default_effort.as_deref())
|
||||||
reasoning_efforts: Vec::new(),
|
.and_then(|s| s.parse().ok()),
|
||||||
|
supports_reasoning_effort: think_efforts.is_some(),
|
||||||
|
reasoning_efforts: think_efforts
|
||||||
|
.map(think_efforts_to_options)
|
||||||
|
.unwrap_or_default(),
|
||||||
capabilities,
|
capabilities,
|
||||||
extra_headers: IndexMap::new(),
|
extra_headers: IndexMap::new(),
|
||||||
context_window,
|
context_window,
|
||||||
@@ -634,6 +675,80 @@ mod tests {
|
|||||||
assert_eq!(result.model, "actual-model-id");
|
assert_eq!(result.model, "actual-model-id");
|
||||||
assert_eq!(result.name.as_deref(), Some("Display Name"));
|
assert_eq!(result.name.as_deref(), Some("Display Name"));
|
||||||
}
|
}
|
||||||
|
/// Live-wire regression: the K3 `/models` entry (api.kimi.com, 2026-07)
|
||||||
|
/// must land in the catalog with selectable low/high/max efforts and a
|
||||||
|
/// max default — this is what feeds `/model <m> [effort]` and `/effort`.
|
||||||
|
#[test]
|
||||||
|
fn platform_entry_maps_live_k3_think_efforts() {
|
||||||
|
use kigi_sampling_types::ReasoningEffort;
|
||||||
|
let wire: kigi_models::WireModel = serde_json::from_value(serde_json::json!({
|
||||||
|
"id": "k3",
|
||||||
|
"display_name": "K3",
|
||||||
|
"context_length": 1_048_576,
|
||||||
|
"supports_reasoning": true,
|
||||||
|
"supports_image_in": true,
|
||||||
|
"supports_video_in": true,
|
||||||
|
"supports_thinking_type": "only",
|
||||||
|
"think_efforts": {
|
||||||
|
"support": true,
|
||||||
|
"valid_efforts": ["low", "high", "max"],
|
||||||
|
"default_effort": "max"
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
let entry = platform_wire_model_to_entry(
|
||||||
|
kigi_models::PlatformId::KimiCode,
|
||||||
|
wire,
|
||||||
|
"https://api.kimi.com/coding/v1",
|
||||||
|
);
|
||||||
|
assert!(entry.supports_reasoning_effort);
|
||||||
|
assert_eq!(entry.reasoning_effort, Some(ReasoningEffort::Xhigh));
|
||||||
|
let ids: Vec<&str> = entry
|
||||||
|
.reasoning_efforts
|
||||||
|
.iter()
|
||||||
|
.map(|o| o.id.as_str())
|
||||||
|
.collect();
|
||||||
|
assert_eq!(
|
||||||
|
ids,
|
||||||
|
["low", "high", "max"],
|
||||||
|
"wire tokens stay the option ids"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
entry
|
||||||
|
.reasoning_efforts
|
||||||
|
.iter()
|
||||||
|
.map(|o| o.value)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
[
|
||||||
|
ReasoningEffort::Low,
|
||||||
|
ReasoningEffort::High,
|
||||||
|
ReasoningEffort::Xhigh
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let max = entry
|
||||||
|
.reasoning_efforts
|
||||||
|
.iter()
|
||||||
|
.find(|o| o.id == "max")
|
||||||
|
.unwrap();
|
||||||
|
assert!(max.default, "max is the server default for K3");
|
||||||
|
assert_eq!(max.label, "Max");
|
||||||
|
// K2.7-style entries (no think_efforts) stay effort-less.
|
||||||
|
let plain: kigi_models::WireModel = serde_json::from_value(serde_json::json!({
|
||||||
|
"id": "kimi-for-coding",
|
||||||
|
"context_length": 262_144,
|
||||||
|
"supports_reasoning": true,
|
||||||
|
"supports_thinking_type": "only"
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
let entry = platform_wire_model_to_entry(
|
||||||
|
kigi_models::PlatformId::KimiCode,
|
||||||
|
plain,
|
||||||
|
"https://api.kimi.com/coding/v1",
|
||||||
|
);
|
||||||
|
assert!(!entry.supports_reasoning_effort);
|
||||||
|
assert!(entry.reasoning_efforts.is_empty());
|
||||||
|
assert!(entry.reasoning_effort.is_none());
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_reads_reasoning_effort_fields() {
|
fn parse_reads_reasoning_effort_fields() {
|
||||||
use kigi_sampling_types::ReasoningEffort;
|
use kigi_sampling_types::ReasoningEffort;
|
||||||
|
|||||||
Reference in New Issue
Block a user