Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src-rust/crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,10 @@ pub mod config {
pub theme: Theme,
#[serde(default)]
pub output_style: Option<String>,
/// Reasoning effort level for new turns (see [`crate::effort::EffortLevel`]).
/// `None` means the query loop's own default (`Medium`) applies.
#[serde(default)]
pub effort: Option<String>,
pub auto_compact: bool,
pub compact_threshold: f32,
pub verbose: bool,
Expand Down Expand Up @@ -1456,6 +1460,13 @@ pub mod config {
}


/// Resolve the configured reasoning effort level, if any and valid.
/// An unset or unparseable value falls back to the query loop's own
/// default (`EffortLevel::Medium`) rather than erroring here.
pub fn effective_effort_level(&self) -> Option<crate::effort::EffortLevel> {
self.effort.as_deref().and_then(crate::effort::EffortLevel::from_str)
}

/// Resolve the effective max-tokens.
pub fn effective_max_tokens(&self) -> u32 {
self.max_tokens
Expand Down Expand Up @@ -1939,6 +1950,7 @@ pub mod config {
permission_mode: over.config.permission_mode,
theme: over.config.theme,
output_style: over.config.output_style.or(base.config.output_style),
effort: over.config.effort.or(base.config.effort),
auto_compact: over.config.auto_compact || base.config.auto_compact,
compact_threshold: if over.config.compact_threshold != 0.0 {
over.config.compact_threshold
Expand Down Expand Up @@ -4761,6 +4773,24 @@ mod tests {
assert_eq!(cfg.effective_model(), "claude-haiku-4-5-20251001");
}

#[test]
fn test_config_effective_effort_level_unset_is_none() {
let cfg = crate::config::Config::default();
assert_eq!(cfg.effective_effort_level(), None);
}

#[test]
fn test_config_effective_effort_level_valid() {
let cfg = crate::config::Config { effort: Some("xhigh".to_string()), ..Default::default() };
assert_eq!(cfg.effective_effort_level(), Some(crate::effort::EffortLevel::XHigh));
}

#[test]
fn test_config_effective_effort_level_invalid_falls_back_to_none() {
let cfg = crate::config::Config { effort: Some("not-a-level".to_string()), ..Default::default() };
assert_eq!(cfg.effective_effort_level(), None);
}

#[test]
fn test_config_effective_max_tokens_default() {
let cfg = crate::config::Config::default();
Expand Down
19 changes: 19 additions & 0 deletions src-rust/crates/query/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ impl QueryConfig {
.as_ref()
.map(|p| p.display().to_string()),
managed_agents: cfg.managed_agents.clone(),
effort_level: cfg.effective_effort_level(),
..Default::default()
}
}
Expand All @@ -231,6 +232,7 @@ impl QueryConfig {
.as_ref()
.map(|p| p.display().to_string()),
managed_agents: cfg.managed_agents.clone(),
effort_level: cfg.effective_effort_level(),
..Default::default()
}
}
Expand Down Expand Up @@ -2122,6 +2124,23 @@ mod tests {
assert_eq!(turn_usage.output_tokens, 75);
}

#[test]
fn from_config_carries_effort_level_through() {
let cfg = claurst_core::config::Config {
effort: Some("high".to_string()),
..Default::default()
};
let query_config = QueryConfig::from_config(&cfg);
assert_eq!(query_config.effort_level, Some(claurst_core::effort::EffortLevel::High));
}

#[test]
fn from_config_unset_effort_is_none() {
let cfg = claurst_core::config::Config::default();
let query_config = QueryConfig::from_config(&cfg);
assert_eq!(query_config.effort_level, None);
}

fn make_config(sys: Option<&str>, append: Option<&str>) -> QueryConfig {
QueryConfig {
model: "claude-sonnet-4-6".to_string(),
Expand Down
48 changes: 45 additions & 3 deletions src-rust/crates/tools/src/config_tool.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// ConfigTool: get or set Claurst configuration settings at runtime.
//
// Reads from and persists to ~/.claurst/settings.json.
// Supported settings: model, max_tokens, verbose, permission_mode.
// Supported settings: model, provider, effort, max_tokens, verbose, permission_mode.

use crate::{PermissionLevel, Tool, ToolContext, ToolResult};
use async_trait::async_trait;
Expand All @@ -18,6 +18,8 @@ struct ConfigInput {

static SUPPORTED_SETTINGS: &[(&str, &str)] = &[
("model", "LLM model to use (e.g. 'claude-opus-4-6')"),
("provider", "Active provider id (e.g. 'anthropic', 'openai', 'google')"),
("effort", "Reasoning effort: none | minimal | low | medium | high | xhigh | max | ultracode"),
("max_tokens", "Maximum output tokens per response"),
("verbose", "Enable verbose logging (true/false)"),
("permission_mode", "Permission mode: default | accept_edits | bypass_permissions | plan"),
Expand All @@ -30,8 +32,8 @@ impl Tool for ConfigTool {

fn description(&self) -> &str {
"Get or set Claurst configuration settings. Omit 'value' to read the current value. \
Supported settings: model, max_tokens, verbose, permission_mode, auto_compact. \
Changes persist to ~/.claurst/settings.json."
Supported settings: model, provider, effort, max_tokens, verbose, permission_mode, \
auto_compact. Changes persist to ~/.claurst/settings.json."
}

fn permission_level(&self) -> PermissionLevel { PermissionLevel::Write }
Expand Down Expand Up @@ -92,6 +94,34 @@ impl Tool for ConfigTool {
}
ToolResult::success(format!("model = \"{}\"", s))
}
"provider" => {
let s = match new_value.as_str() {
Some(s) => s.to_string(),
None => return ToolResult::error("'provider' must be a string".to_string()),
};
settings.config.provider = Some(s.clone());
if let Err(e) = settings.save().await {
return ToolResult::error(format!("Failed to save settings: {}", e));
}
ToolResult::success(format!("provider = \"{}\"", s))
}
"effort" => {
let s = match new_value.as_str() {
Some(s) => s,
None => return ToolResult::error("'effort' must be a string".to_string()),
};
if claurst_core::effort::EffortLevel::from_str(s).is_none() {
return ToolResult::error(format!(
"Unknown effort level '{}'. Use: none | minimal | low | medium | high | xhigh | max | ultracode",
s
));
}
settings.config.effort = Some(s.to_string());
if let Err(e) = settings.save().await {
return ToolResult::error(format!("Failed to save settings: {}", e));
}
ToolResult::success(format!("effort = \"{}\"", s))
}
"max_tokens" => {
let n = match new_value.as_u64() {
Some(n) => n as u32,
Expand Down Expand Up @@ -163,6 +193,18 @@ impl Tool for ConfigTool {
"model = \"{}\"",
settings.config.effective_model()
)),
"provider" => ToolResult::success(format!(
"provider = \"{}\"",
settings.config.selected_provider_id()
)),
"effort" => ToolResult::success(format!(
"effort = \"{}\"",
settings
.config
.effective_effort_level()
.unwrap_or_default()
.as_str()
)),
"max_tokens" => ToolResult::success(format!(
"max_tokens = {}",
settings.config.effective_max_tokens()
Expand Down
Loading