From f8df58966bc0aaded8355b9dc0dcee5d6b026c65 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Thu, 10 Sep 2026 09:44:11 -0600 Subject: [PATCH] Use the lithos closest_supported_effort on ModelCapabilities The reasoning-effort substitution rule now lives on lithos's ModelCapabilities, so the workflow fallback planner calls it directly and fabro-types drops its controls module. ReasoningEffort is re-exported from lithos alongside the other request types. Co-Authored-By: Claude Fable 5.1 --- .../fabro-workflow/src/handler/llm/api.rs | 6 +- lib/foundation/fabro-types/src/controls.rs | 76 ------------------- lib/foundation/fabro-types/src/lib.rs | 6 +- 3 files changed, 4 insertions(+), 84 deletions(-) delete mode 100644 lib/foundation/fabro-types/src/controls.rs diff --git a/lib/components/fabro-workflow/src/handler/llm/api.rs b/lib/components/fabro-workflow/src/handler/llm/api.rs index c2eeec956..afc017684 100644 --- a/lib/components/fabro-workflow/src/handler/llm/api.rs +++ b/lib/components/fabro-workflow/src/handler/llm/api.rs @@ -20,7 +20,7 @@ use fabro_types::settings::run::RunModelControls; use fabro_types::{ AgentProfileKind, FailoverProps, Message, ModelHandle, ModelId, ModelRef, PermissionLevel, ProviderId, ReasoningEffort, Role, RunId, SessionCapability, Speed, StageId, StageTiming, - TokenCounts, ToolDefinition as LlmToolDefinition, UsdMicros, billing, controls, + TokenCounts, ToolDefinition as LlmToolDefinition, UsdMicros, billing, }; use serde::de::DeserializeOwned; use tokio::sync::mpsc; @@ -897,9 +897,7 @@ impl AgentApiBackend { return FallbackControls::Usable(requested); }; let capabilities = offering.model.capabilities(); - let effective_effort = controls::closest_supported_effort(requested_effort, |effort| { - capabilities.reasoning_effort(effort).is_supported() - }); + let effective_effort = capabilities.closest_supported_effort(requested_effort); match effective_effort { Some(effort) => FallbackControls::Usable(EffectiveRequestControls { reasoning_effort: Some(effort), diff --git a/lib/foundation/fabro-types/src/controls.rs b/lib/foundation/fabro-types/src/controls.rs deleted file mode 100644 index 0017f6bc7..000000000 --- a/lib/foundation/fabro-types/src/controls.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! Helpers over the lithos request-control enums. -//! -//! lithos owns [`ReasoningEffort`] and [`Speed`], their spellings, and their -//! parsing (`ALL`, `as_str`, `Display`, `FromStr`). What stays here is -//! Fabro's own rule for substituting a reasoning level a model lacks. - -pub use lithos_llm::types::{ReasoningEffort, Speed}; - -/// Position of an effort in the least-to-most ordering. -fn effort_rank(effort: ReasoningEffort) -> usize { - ReasoningEffort::ALL - .iter() - .position(|candidate| *candidate == effort) - .unwrap_or(ReasoningEffort::ALL.len()) -} - -/// Selects the supported effort nearest to `requested`. -/// -/// When two supported values are equally distant, the higher effort wins. -/// Returns `None` when nothing is supported. -#[must_use] -pub fn closest_supported_effort( - requested: ReasoningEffort, - supported: impl Fn(ReasoningEffort) -> bool, -) -> Option { - let target = effort_rank(requested); - ReasoningEffort::ALL - .into_iter() - .filter(|effort| supported(*effort)) - .min_by_key(|effort| { - let rank = effort_rank(*effort); - (rank.abs_diff(target), std::cmp::Reverse(rank)) - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn lithos_spellings_match_serde() { - for effort in ReasoningEffort::ALL { - let json = serde_json::to_string(&effort).unwrap(); - assert_eq!(json, format!("\"{effort}\"")); - assert_eq!(effort.as_str().parse::().unwrap(), effort); - } - for speed in Speed::ALL { - let json = serde_json::to_string(&speed).unwrap(); - assert_eq!(json, format!("\"{speed}\"")); - assert_eq!(speed.as_str().parse::().unwrap(), speed); - } - assert!("standard".parse::().is_err()); - assert!("standard".parse::().is_err()); - } - - #[test] - fn closest_supported_prefers_the_higher_neighbor_on_ties() { - let supported = |effort| matches!(effort, ReasoningEffort::Low | ReasoningEffort::High); - assert_eq!( - closest_supported_effort(ReasoningEffort::Medium, supported), - Some(ReasoningEffort::High) - ); - assert_eq!( - closest_supported_effort(ReasoningEffort::Max, supported), - Some(ReasoningEffort::High) - ); - assert_eq!( - closest_supported_effort(ReasoningEffort::Minimal, supported), - Some(ReasoningEffort::Low) - ); - assert_eq!( - closest_supported_effort(ReasoningEffort::Medium, |_| false), - None - ); - } -} diff --git a/lib/foundation/fabro-types/src/lib.rs b/lib/foundation/fabro-types/src/lib.rs index 58f99418c..ce95b8e18 100644 --- a/lib/foundation/fabro-types/src/lib.rs +++ b/lib/foundation/fabro-types/src/lib.rs @@ -11,7 +11,6 @@ pub mod catalog_api; pub mod checkpoint; pub mod command_output; pub mod conclusion; -pub mod controls; pub mod dense; pub mod diff; pub mod event_envelope; @@ -76,7 +75,6 @@ pub use catalog_api::{Model, ModelControls, ModelCosts, ModelFeatures, ModelLimi pub use checkpoint::Checkpoint; pub use command_output::{CommandOutputStream, CommandTermination}; pub use conclusion::{Conclusion, StageSummary}; -pub use controls::ReasoningEffort; pub use dense::{ServerSettings, UserSettings, WorkflowSettings}; pub use diff::{DiffStats, DiffSummary, RunDiff}; pub use event_envelope::EventEnvelope; @@ -94,8 +92,8 @@ pub use interview::{ }; pub use lithos_llm::catalog::{ModelHandle, ModelId, ProviderId}; pub use lithos_llm::types::{ - FinishReason, ReasoningOutput, Request, RequestBuildError, RequestBuilder, Response, - ResponseFormat, StreamEvent, + FinishReason, ReasoningEffort, ReasoningOutput, Request, RequestBuildError, RequestBuilder, + Response, ResponseFormat, StreamEvent, }; pub use llm_backend::AgentBackend; pub use manifest_path::{ManifestPath, ManifestPathParseError};