Split canonical ReasoningEffort::Max out of Xhigh (providers P0c-1)
OpenAI (Responses) and Anthropic (Messages) treat xhigh and max as
DISTINCT effort levels in 2026, and the Kimi K3 wire's top tier is max —
the old parse alias (max→Xhigh) conflated them. Canonical Max now exists:
parse/as_str/serde split, Messages mapping sends xhigh and max as their
own tokens (was Xhigh→"max"), and the K3 menu token max carries
canonical Max end to end.
Kimi wire is byte-identical in all four flows (menu pick, restored
legacy xhigh session, --reasoning-effort flag, /effort command) —
adversarially traced and pinned: kimi_compat's string-level xhigh→max
rename covers legacy tokens, max passes through verbatim.
From the review:
- Rollback safety: persisted reasoning_effort (session summaries, chat
history) deserializes leniently — unknown future tokens degrade to
None with a warning instead of hiding sessions or failing resume.
- Restore migration: a pre-split xhigh override onto a model whose menu
offers max but not xhigh (K3) migrates once, healing display/active-row
drift and re-persisting the live vocabulary.
- /effort max now rejects (with the offered list) on models whose menu
lacks a max row instead of silently applying xhigh; deliberate, tested.
- The interim Responses-backend Max→xhigh downgrade (async-openai has no
Max variant through 0.41) warns loudly; real max wiring lands with the
OpenAI provider cycle via post-serialize body patch.
- Two rusted ignored-e2e wire pins asserted the pre-adapt reasoning_effort
key (deleted by the body adapter since ea0ce9d); they now pin the real
thinking.effort=max shape.
This commit is contained in:
@@ -879,7 +879,14 @@ pub struct Summary {
|
||||
/// `None` for sessions created before this field existed.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub sandbox_profile: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
/// Lenient on read: an unknown token written by a newer kigi (grown
|
||||
/// effort vocabulary) drops to `None` rather than hiding the whole
|
||||
/// session from listings / failing resume after a version rollback.
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "kigi_sampling_types::lenient_reasoning_effort_opt"
|
||||
)]
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
}
|
||||
|
||||
@@ -1002,6 +1009,22 @@ mod is_hidden_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn summary_unknown_reasoning_effort_token_degrades_to_none() {
|
||||
// A summary written by a NEWER kigi with a grown effort vocabulary
|
||||
// must not vanish from listings or fail resume on this binary.
|
||||
let mut s = summary_with_kind(None);
|
||||
s.reasoning_effort = Some(ReasoningEffort::Max);
|
||||
let json = serde_json::to_string(&s).unwrap();
|
||||
let future = json.replace("\"max\"", "\"hypermax\"");
|
||||
assert_ne!(json, future, "fixture must actually carry the token");
|
||||
let back: Summary = serde_json::from_str(&future).expect("record must survive");
|
||||
assert_eq!(back.reasoning_effort, None);
|
||||
// Known tokens (including post-split "max") still round-trip.
|
||||
let back: Summary = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(back.reasoning_effort, Some(ReasoningEffort::Max));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn summary_round_trips_and_defaults_reasoning_effort() {
|
||||
let mut s = summary_with_kind(None);
|
||||
|
||||
Reference in New Issue
Block a user