use serde::{Deserialize, Serialize};
use crate::driver::{ModelSpec, ReasoningConfig};
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct TurnControls {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<ModelSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning: Option<ReasoningConfig>,
}
impl TurnControls {
pub fn new() -> Self {
Self::default()
}
pub fn model(mut self, model: ModelSpec) -> Self {
self.model = Some(model);
self
}
pub fn reasoning_effort(mut self, effort: impl Into<String>) -> Self {
let mut reasoning = self.reasoning.unwrap_or_default();
reasoning.effort = Some(effort.into());
self.reasoning = Some(reasoning);
self
}
pub fn thinking_budget(mut self, budget_tokens: u32) -> Self {
let mut reasoning = self.reasoning.unwrap_or_default();
reasoning.budget_tokens = Some(budget_tokens);
self.reasoning = Some(reasoning);
self
}
pub fn resolve(&self, default_model: &ModelSpec) -> ModelSpec {
let mut model = self.model.clone().unwrap_or_else(|| default_model.clone());
if let Some(reasoning) = &self.reasoning {
model.reasoning = Some(reasoning.clone());
}
model
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::driver::DriverId;
#[test]
fn no_controls_keeps_the_default_model() {
let default_model = ModelSpec::new(DriverId::openai(), "gpt-5.5");
let resolved = TurnControls::new().resolve(&default_model);
assert_eq!(resolved, default_model);
}
#[test]
fn model_override_replaces_the_default() {
let default_model = ModelSpec::new(DriverId::openai(), "gpt-5.5");
let override_model = ModelSpec::new(DriverId::anthropic(), "claude-sonnet-4-5");
let resolved = TurnControls::new()
.model(override_model.clone())
.resolve(&default_model);
assert_eq!(resolved, override_model);
}
#[test]
fn reasoning_effort_layers_onto_whichever_model_is_chosen() {
let default_model = ModelSpec::new(DriverId::openai(), "gpt-5.5");
let resolved = TurnControls::new()
.reasoning_effort("high")
.resolve(&default_model);
assert_eq!(resolved.model, "gpt-5.5");
assert_eq!(resolved.reasoning.unwrap().effort, Some("high".to_string()));
}
}