From 605d69e3ae83acca0f0c19050492d4a1bc16a960 Mon Sep 17 00:00:00 2001 From: Tushar Date: Tue, 31 Mar 2026 12:40:39 +0530 Subject: [PATCH 1/3] feat(config): add support for global reasoning configuration and effort levels --- .../transformers/set_reasoning_effort.rs | 11 ++- crates/forge_app/src/orch_spec/orch_setup.rs | 1 + crates/forge_config/.forge.toml | 4 + crates/forge_config/src/config.rs | 7 +- crates/forge_config/src/lib.rs | 2 + crates/forge_config/src/reasoning.rs | 52 +++++++++++ crates/forge_domain/src/agent.rs | 13 +++ crates/forge_domain/src/agent_definition.rs | 56 +++++++++--- crates/forge_domain/src/env.rs | 10 ++- crates/forge_infra/src/env.rs | 54 +++++++++++- .../src/conversation/conversation_record.rs | 22 +++-- .../src/provider/openai_responses/request.rs | 11 ++- forge.schema.json | 86 +++++++++++++++++++ 13 files changed, 299 insertions(+), 30 deletions(-) create mode 100644 crates/forge_config/src/reasoning.rs diff --git a/crates/forge_app/src/dto/openai/transformers/set_reasoning_effort.rs b/crates/forge_app/src/dto/openai/transformers/set_reasoning_effort.rs index d6f2a959ba..eec5b1dfb5 100644 --- a/crates/forge_app/src/dto/openai/transformers/set_reasoning_effort.rs +++ b/crates/forge_app/src/dto/openai/transformers/set_reasoning_effort.rs @@ -11,11 +11,14 @@ use crate::dto::openai::Request; /// # Transformation Rules /// /// - If `reasoning.enabled == Some(false)` → use "none" (disables reasoning) -/// - If `reasoning.effort` is set (low/medium/high) → use that value +/// - If `reasoning.effort` is set (none/minimal/low/medium/high/xhigh) → use that value /// - If `reasoning.max_tokens` is set (thinking budget) → convert to effort: -/// - 0-1024 → "low" -/// - 1025-8192 → "medium" -/// - 8193+ → "high" +/// - 0 → "none" +/// - 1–512 → "minimal" +/// - 513–1024 → "low" +/// - 1025–8192 → "medium" +/// - 8193–32768 → "high" +/// - 32769+ → "xhigh" /// - If `reasoning.enabled == Some(true)` but no other params → default to /// "medium" /// - Original `reasoning` field is removed after transformation diff --git a/crates/forge_app/src/orch_spec/orch_setup.rs b/crates/forge_app/src/orch_spec/orch_setup.rs index f03ff78aca..295ee02aa2 100644 --- a/crates/forge_app/src/orch_spec/orch_setup.rs +++ b/crates/forge_app/src/orch_spec/orch_setup.rs @@ -106,6 +106,7 @@ impl Default for TestContext { max_requests_per_turn: None, compact: None, updates: None, + reasoning: None, }, title: Some("test-conversation".into()), agent: Agent::new( diff --git a/crates/forge_config/.forge.toml b/crates/forge_config/.forge.toml index fa2331e690..f324b6c9ab 100644 --- a/crates/forge_config/.forge.toml +++ b/crates/forge_config/.forge.toml @@ -59,3 +59,7 @@ token_threshold = 100000 [updates] auto_update = true frequency = "daily" + +[reasoning] +enabled = true +effort = "medium" diff --git a/crates/forge_config/src/config.rs b/crates/forge_config/src/config.rs index 366633054e..7bd4982365 100644 --- a/crates/forge_config/src/config.rs +++ b/crates/forge_config/src/config.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::reader::ConfigReader; use crate::writer::ConfigWriter; -use crate::{AutoDumpFormat, Compact, Decimal, HttpConfig, ModelConfig, RetryConfig, Update}; +use crate::{AutoDumpFormat, Compact, Decimal, HttpConfig, ModelConfig, ReasoningConfig, RetryConfig, Update}; /// Top-level Forge configuration merged from all sources (defaults, file, /// environment). @@ -159,6 +159,11 @@ pub struct ForgeConfig { /// all tool calls are disabled. #[serde(default, skip_serializing_if = "Option::is_none")] pub tool_supported: Option, + + /// Reasoning configuration applied to all agents; agent-level settings + /// take priority over this global setting when both are present. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reasoning: Option, } #[cfg(test)] diff --git a/crates/forge_config/src/lib.rs b/crates/forge_config/src/lib.rs index 6bcbdaa147..cc253277e4 100644 --- a/crates/forge_config/src/lib.rs +++ b/crates/forge_config/src/lib.rs @@ -8,6 +8,7 @@ mod legacy; mod model; mod percentage; mod reader; +mod reasoning; mod retry; mod writer; @@ -20,6 +21,7 @@ pub use http::*; pub use model::*; pub use percentage::*; pub use reader::*; +pub use reasoning::*; pub use retry::*; pub use writer::*; diff --git a/crates/forge_config/src/reasoning.rs b/crates/forge_config/src/reasoning.rs new file mode 100644 index 0000000000..9deed53fab --- /dev/null +++ b/crates/forge_config/src/reasoning.rs @@ -0,0 +1,52 @@ +use fake::Dummy; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Effort level for the reasoning capability. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Dummy)] +#[serde(rename_all = "lowercase")] +pub enum Effort { + /// No reasoning tokens; disables extended thinking entirely. + None, + /// Minimal reasoning; fastest, fewest thinking tokens. + Minimal, + /// Constrained reasoning suitable for straightforward tasks. + Low, + /// Balanced reasoning for moderately complex tasks. + Medium, + /// Deep reasoning for complex problems. + High, + /// Maximum reasoning budget for the hardest tasks. + #[serde(rename = "xhigh")] + XHigh, +} + +/// Reasoning configuration applied to all agents when set at the global level. +/// +/// Controls the reasoning capabilities of the model. When set here, it acts as +/// a default for all agents; agent-level settings take priority over this +/// global setting. +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Dummy)] +#[serde(rename_all = "snake_case")] +pub struct ReasoningConfig { + /// Controls the effort level of the agent's reasoning. + /// Supported by openrouter and forge provider. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub effort: Option, + + /// Controls how many tokens the model can spend thinking. + /// Supported by openrouter, anthropic and forge provider. + /// Should be greater than 1024 but less than overall max_tokens. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub max_tokens: Option, + + /// Model thinks deeply, but the reasoning is hidden from you. + /// Supported by openrouter and forge provider. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub exclude: Option, + + /// Enables reasoning at the "medium" effort level with no exclusions. + /// Supported by openrouter, anthropic and forge provider. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, +} diff --git a/crates/forge_domain/src/agent.rs b/crates/forge_domain/src/agent.rs index e6a2e3340d..0df1040e16 100644 --- a/crates/forge_domain/src/agent.rs +++ b/crates/forge_domain/src/agent.rs @@ -156,6 +156,19 @@ impl Agent { agent.compact = merged_compact; } + // Apply workflow reasoning configuration to agents + // Agent-level settings take priority; env fills in any unset fields. + if let Some(ref env_reasoning) = env.reasoning { + let merged = match agent.reasoning.take() { + Some(mut agent_reasoning) => { + agent_reasoning.merge(env_reasoning.clone()); + agent_reasoning + } + None => env_reasoning.clone(), + }; + agent.reasoning = Some(merged); + } + agent } diff --git a/crates/forge_domain/src/agent_definition.rs b/crates/forge_domain/src/agent_definition.rs index ec7165f63b..3234a09ab3 100644 --- a/crates/forge_domain/src/agent_definition.rs +++ b/crates/forge_domain/src/agent_definition.rs @@ -217,23 +217,43 @@ pub struct ReasoningConfig { #[serde(rename_all = "lowercase")] #[strum(serialize_all = "lowercase")] pub enum Effort { - High, - Medium, + /// No reasoning tokens; disables extended thinking entirely. + None, + /// Minimal reasoning; fastest, fewest thinking tokens (1–512). + Minimal, + /// Constrained reasoning suitable for straightforward tasks (513–1024). Low, + /// Balanced reasoning for moderately complex tasks (1025–8192). + Medium, + /// Deep reasoning for complex problems (8193–32768). + High, + /// Maximum reasoning budget for the hardest tasks (32769+). + #[serde(rename = "xhigh")] + #[strum(serialize = "xhigh")] + XHigh, } /// Converts a thinking budget (max_tokens) to Effort -/// - 0-1024 → Low -/// - 1025-8192 → Medium -/// - 8193+ → High +/// - 0 → None +/// - 1–512 → Minimal +/// - 513–1024 → Low +/// - 1025–8192 → Medium +/// - 8193–32768 → High +/// - 32769+ → XHigh impl From for Effort { fn from(budget: usize) -> Self { - if budget <= 1024 { + if budget == 0 { + Effort::None + } else if budget <= 512 { + Effort::Minimal + } else if budget <= 1024 { Effort::Low } else if budget <= 8192 { Effort::Medium - } else { + } else if budget <= 32768 { Effort::High + } else { + Effort::XHigh } } } @@ -294,10 +314,20 @@ mod tests { use super::*; + #[test] + fn test_effort_from_budget_none() { + assert_eq!(Effort::from(0), Effort::None); + } + + #[test] + fn test_effort_from_budget_minimal() { + assert_eq!(Effort::from(1), Effort::Minimal); + assert_eq!(Effort::from(512), Effort::Minimal); + } + #[test] fn test_effort_from_budget_low() { - assert_eq!(Effort::from(0), Effort::Low); - assert_eq!(Effort::from(1), Effort::Low); + assert_eq!(Effort::from(513), Effort::Low); assert_eq!(Effort::from(1024), Effort::Low); } @@ -312,7 +342,13 @@ mod tests { fn test_effort_from_budget_high() { assert_eq!(Effort::from(8193), Effort::High); assert_eq!(Effort::from(10000), Effort::High); - assert_eq!(Effort::from(100000), Effort::High); + assert_eq!(Effort::from(32768), Effort::High); + } + + #[test] + fn test_effort_from_budget_xhigh() { + assert_eq!(Effort::from(32769), Effort::XHigh); + assert_eq!(Effort::from(100000), Effort::XHigh); } #[test] diff --git a/crates/forge_domain/src/env.rs b/crates/forge_domain/src/env.rs index 1db2b2903c..1953d804f2 100644 --- a/crates/forge_domain/src/env.rs +++ b/crates/forge_domain/src/env.rs @@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize}; use url::Url; use crate::{ - CommitConfig, Compact, HttpConfig, MaxTokens, ModelId, ProviderId, RetryConfig, SuggestConfig, - Temperature, TopK, TopP, Update, + CommitConfig, Compact, HttpConfig, MaxTokens, ModelId, ProviderId, ReasoningConfig, RetryConfig, + SuggestConfig, Temperature, TopK, TopP, Update, }; /// Domain-level session configuration pairing a provider with a model. @@ -191,6 +191,12 @@ pub struct Environment { #[serde(default, skip_serializing_if = "Option::is_none")] pub compact: Option, + /// Reasoning configuration applied to all agents; agent-level settings + /// take priority over this global setting when both are present. + #[dummy(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reasoning: Option, + /// Configuration for automatic forge updates. #[dummy(default)] #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/crates/forge_infra/src/env.rs b/crates/forge_infra/src/env.rs index aeb5882838..b99a6750cd 100644 --- a/crates/forge_infra/src/env.rs +++ b/crates/forge_infra/src/env.rs @@ -5,9 +5,9 @@ use std::sync::Arc; use forge_app::EnvironmentInfra; use forge_config::{ConfigReader, ForgeConfig, ModelConfig}; use forge_domain::{ - AutoDumpFormat, Compact, ConfigOperation, Environment, HttpConfig, MaxTokens, ModelId, - RetryConfig, SessionConfig, Temperature, TlsBackend, TlsVersion, TopK, TopP, Update, - UpdateFrequency, + AutoDumpFormat, Compact, ConfigOperation, Effort, Environment, HttpConfig, MaxTokens, ModelId, + ReasoningConfig, RetryConfig, SessionConfig, Temperature, TlsBackend, TlsVersion, TopK, TopP, + Update, UpdateFrequency, }; use reqwest::Url; use tracing::{debug, error}; @@ -114,6 +114,52 @@ fn to_compact(c: forge_config::Compact) -> Compact { } } +/// Converts a [`forge_config::Effort`] into a [`forge_domain::Effort`]. +fn to_effort(e: forge_config::Effort) -> Effort { + match e { + forge_config::Effort::None => Effort::None, + forge_config::Effort::Minimal => Effort::Minimal, + forge_config::Effort::Low => Effort::Low, + forge_config::Effort::Medium => Effort::Medium, + forge_config::Effort::High => Effort::High, + forge_config::Effort::XHigh => Effort::XHigh, + } +} + +/// Converts a [`forge_domain::Effort`] back into a [`forge_config::Effort`]. +fn from_effort(e: Effort) -> forge_config::Effort { + match e { + Effort::None => forge_config::Effort::None, + Effort::Minimal => forge_config::Effort::Minimal, + Effort::Low => forge_config::Effort::Low, + Effort::Medium => forge_config::Effort::Medium, + Effort::High => forge_config::Effort::High, + Effort::XHigh => forge_config::Effort::XHigh, + } +} + +/// Converts a [`forge_config::ReasoningConfig`] into a +/// [`forge_domain::ReasoningConfig`]. +fn to_reasoning_config(r: forge_config::ReasoningConfig) -> ReasoningConfig { + ReasoningConfig { + effort: r.effort.map(to_effort), + max_tokens: r.max_tokens, + exclude: r.exclude, + enabled: r.enabled, + } +} + +/// Converts a [`forge_domain::ReasoningConfig`] back into a +/// [`forge_config::ReasoningConfig`]. +fn from_reasoning_config(r: &ReasoningConfig) -> forge_config::ReasoningConfig { + forge_config::ReasoningConfig { + effort: r.effort.clone().map(from_effort), + max_tokens: r.max_tokens, + exclude: r.exclude, + enabled: r.enabled, + } +} + /// Builds a [`forge_domain::Environment`] entirely from a [`ForgeConfig`] and /// runtime context (`restricted`, `cwd`), mapping every config field to its /// corresponding environment field. @@ -178,6 +224,7 @@ fn to_environment(fc: ForgeConfig, cwd: PathBuf) -> Environment { max_requests_per_turn: fc.max_requests_per_turn, compact: fc.compact.map(to_compact), updates: fc.updates.map(to_update), + reasoning: fc.reasoning.map(to_reasoning_config), } } @@ -339,6 +386,7 @@ fn to_forge_config(env: &Environment) -> ForgeConfig { fc.max_requests_per_turn = env.max_requests_per_turn; fc.compact = env.compact.as_ref().map(from_compact); fc.updates = env.updates.as_ref().map(from_update); + fc.reasoning = env.reasoning.as_ref().map(from_reasoning_config); // --- Session configs --- fc.session = env.session.as_ref().map(|sc| ModelConfig { diff --git a/crates/forge_repo/src/conversation/conversation_record.rs b/crates/forge_repo/src/conversation/conversation_record.rs index c8d0a263b4..614b6f8b0f 100644 --- a/crates/forge_repo/src/conversation/conversation_record.rs +++ b/crates/forge_repo/src/conversation/conversation_record.rs @@ -647,17 +647,24 @@ impl From for forge_domain::ToolChoice { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub(super) enum EffortRecord { - High, - Medium, + None, + Minimal, Low, + Medium, + High, + #[serde(rename = "xhigh")] + XHigh, } impl From<&forge_domain::Effort> for EffortRecord { fn from(effort: &forge_domain::Effort) -> Self { match effort { - forge_domain::Effort::High => Self::High, - forge_domain::Effort::Medium => Self::Medium, + forge_domain::Effort::None => Self::None, + forge_domain::Effort::Minimal => Self::Minimal, forge_domain::Effort::Low => Self::Low, + forge_domain::Effort::Medium => Self::Medium, + forge_domain::Effort::High => Self::High, + forge_domain::Effort::XHigh => Self::XHigh, } } } @@ -665,9 +672,12 @@ impl From<&forge_domain::Effort> for EffortRecord { impl From for forge_domain::Effort { fn from(record: EffortRecord) -> Self { match record { - EffortRecord::High => Self::High, - EffortRecord::Medium => Self::Medium, + EffortRecord::None => Self::None, + EffortRecord::Minimal => Self::Minimal, EffortRecord::Low => Self::Low, + EffortRecord::Medium => Self::Medium, + EffortRecord::High => Self::High, + EffortRecord::XHigh => Self::XHigh, } } } diff --git a/crates/forge_repo/src/provider/openai_responses/request.rs b/crates/forge_repo/src/provider/openai_responses/request.rs index 07546c5933..e576c975a6 100644 --- a/crates/forge_repo/src/provider/openai_responses/request.rs +++ b/crates/forge_repo/src/provider/openai_responses/request.rs @@ -113,11 +113,14 @@ impl FromDomain for oai::Reasoning { // Map effort level if let Some(effort) = config.effort { let oai_effort = match effort { - Effort::High => oai::ReasoningEffort::High, - Effort::Medium => oai::ReasoningEffort::Medium, - Effort::Low => oai::ReasoningEffort::Low, + Effort::XHigh | Effort::High => Some(oai::ReasoningEffort::High), + Effort::Medium => Some(oai::ReasoningEffort::Medium), + Effort::Low | Effort::Minimal => Some(oai::ReasoningEffort::Low), + Effort::None => None, }; - builder.effort(oai_effort); + if let Some(oai_effort) = oai_effort { + builder.effort(oai_effort); + } } else if config.enabled.unwrap_or(false) { // Default to Medium effort when enabled without explicit effort builder.effort(oai::ReasoningEffort::Medium); diff --git a/forge.schema.json b/forge.schema.json index b529d71e07..f86a7d78b2 100644 --- a/forge.schema.json +++ b/forge.schema.json @@ -240,6 +240,17 @@ "format": "uint64", "minimum": 0 }, + "reasoning": { + "description": "Reasoning configuration applied to all agents; agent-level settings\ntake priority over this global setting when both are present.", + "anyOf": [ + { + "$ref": "#/$defs/ReasoningConfig" + }, + { + "type": "null" + } + ] + }, "restricted": { "description": "Whether restricted mode is active; when enabled, tool execution requires\nexplicit permission grants.", "type": [ @@ -439,6 +450,41 @@ } } }, + "Effort": { + "description": "Effort level for the reasoning capability.", + "oneOf": [ + { + "description": "No reasoning tokens; disables extended thinking entirely.", + "type": "string", + "const": "none" + }, + { + "description": "Minimal reasoning; fastest, fewest thinking tokens.", + "type": "string", + "const": "minimal" + }, + { + "description": "Constrained reasoning suitable for straightforward tasks.", + "type": "string", + "const": "low" + }, + { + "description": "Balanced reasoning for moderately complex tasks.", + "type": "string", + "const": "medium" + }, + { + "description": "Deep reasoning for complex problems.", + "type": "string", + "const": "high" + }, + { + "description": "Maximum reasoning budget for the hardest tasks.", + "type": "string", + "const": "xhigh" + } + ] + }, "HttpConfig": { "description": "HTTP client configuration.", "type": "object", @@ -568,6 +614,46 @@ } } }, + "ReasoningConfig": { + "description": "Reasoning configuration applied to all agents when set at the global level.\n\nControls the reasoning capabilities of the model. When set here, it acts as\na default for all agents; agent-level settings take priority over this\nglobal setting.", + "type": "object", + "properties": { + "effort": { + "description": "Controls the effort level of the agent's reasoning.\nSupported by openrouter and forge provider.", + "anyOf": [ + { + "$ref": "#/$defs/Effort" + }, + { + "type": "null" + } + ] + }, + "enabled": { + "description": "Enables reasoning at the \"medium\" effort level with no exclusions.\nSupported by openrouter, anthropic and forge provider.", + "type": [ + "boolean", + "null" + ] + }, + "exclude": { + "description": "Model thinks deeply, but the reasoning is hidden from you.\nSupported by openrouter and forge provider.", + "type": [ + "boolean", + "null" + ] + }, + "max_tokens": { + "description": "Controls how many tokens the model can spend thinking.\nSupported by openrouter, anthropic and forge provider.\nShould be greater than 1024 but less than overall max_tokens.", + "type": [ + "integer", + "null" + ], + "format": "uint", + "minimum": 0 + } + } + }, "RetryConfig": { "description": "Configuration for retry mechanism.", "type": "object", From ab6a93a949934332fadd531a2d6c6b1d144c0db6 Mon Sep 17 00:00:00 2001 From: Tushar Date: Tue, 31 Mar 2026 14:03:29 +0530 Subject: [PATCH 2/3] feat(config): add global reasoning configuration and effort level handling --- crates/forge_api/src/api.rs | 11 +++ crates/forge_api/src/forge_api.rs | 8 ++ crates/forge_app/src/command_generator.rs | 13 +++ crates/forge_app/src/dto/anthropic/request.rs | 34 +++++--- crates/forge_app/src/dto/google/request.rs | 38 +++++--- crates/forge_app/src/services.rs | 24 ++++++ crates/forge_domain/src/env.rs | 14 +++ crates/forge_main/src/built_in_commands.json | 8 ++ crates/forge_main/src/cli.rs | 70 +++++++++++++++ crates/forge_main/src/ui.rs | 23 +++++ crates/forge_repo/src/provider/bedrock.rs | 66 ++++++++------ crates/forge_services/src/app_config.rs | 15 +++- shell-plugin/forge.theme.zsh | 1 + shell-plugin/lib/actions/config.zsh | 86 +++++++++++++++++++ shell-plugin/lib/config.zsh | 5 ++ shell-plugin/lib/dispatcher.zsh | 6 ++ shell-plugin/lib/helpers.zsh | 2 + 17 files changed, 375 insertions(+), 49 deletions(-) diff --git a/crates/forge_api/src/api.rs b/crates/forge_api/src/api.rs index cf8306a491..1a7021e791 100644 --- a/crates/forge_api/src/api.rs +++ b/crates/forge_api/src/api.rs @@ -148,6 +148,17 @@ pub trait API: Sync + Send { /// suggestion generation). async fn set_suggest_config(&self, config: forge_domain::SuggestConfig) -> anyhow::Result<()>; + /// Gets the global reasoning configuration. + async fn get_reasoning_config( + &self, + ) -> anyhow::Result>; + + /// Sets the global reasoning configuration. + async fn set_reasoning_config( + &self, + config: forge_domain::ReasoningConfig, + ) -> anyhow::Result<()>; + /// Refresh MCP caches by fetching fresh data async fn reload_mcp(&self) -> Result<()>; diff --git a/crates/forge_api/src/forge_api.rs b/crates/forge_api/src/forge_api.rs index 1aa7ff6d91..b38589f5b9 100644 --- a/crates/forge_api/src/forge_api.rs +++ b/crates/forge_api/src/forge_api.rs @@ -277,6 +277,14 @@ impl anyhow::Result> { + self.services.get_reasoning_config().await + } + + async fn set_reasoning_config(&self, config: ReasoningConfig) -> anyhow::Result<()> { + self.services.set_reasoning_config(config).await + } + async fn reload_mcp(&self) -> Result<()> { self.services.mcp_service().reload_mcp().await } diff --git a/crates/forge_app/src/command_generator.rs b/crates/forge_app/src/command_generator.rs index 7650deef19..06d6db1ae7 100644 --- a/crates/forge_app/src/command_generator.rs +++ b/crates/forge_app/src/command_generator.rs @@ -279,6 +279,19 @@ mod tests { async fn set_suggest_config(&self, _config: forge_domain::SuggestConfig) -> Result<()> { Ok(()) } + + async fn get_reasoning_config( + &self, + ) -> Result> { + Ok(None) + } + + async fn set_reasoning_config( + &self, + _config: forge_domain::ReasoningConfig, + ) -> Result<()> { + Ok(()) + } } #[tokio::test] diff --git a/crates/forge_app/src/dto/anthropic/request.rs b/crates/forge_app/src/dto/anthropic/request.rs index 293a57961b..e730f8f582 100644 --- a/crates/forge_app/src/dto/anthropic/request.rs +++ b/crates/forge_app/src/dto/anthropic/request.rs @@ -116,16 +116,30 @@ impl TryFrom for Request { tool_choice: request.tool_choice.map(ToolChoice::from), stream: Some(request.stream.unwrap_or(true)), thinking: request.reasoning.and_then(|reasoning| { - reasoning.enabled.and_then(|enabled| { - if enabled { - Some(Thinking { - r#type: ThinkingType::Enabled, - budget_tokens: reasoning.max_tokens.unwrap_or(10000) as u64, - }) - } else { - None - } - }) + use forge_domain::Effort; + // Effort::None explicitly disables thinking + if matches!(reasoning.effort, Some(Effort::None)) { + return None; + } + // Map effort variant to a token budget; takes priority over max_tokens + let effort_budget: Option = reasoning.effort.as_ref().map(|e| match e { + Effort::None => 0, // unreachable — handled above + Effort::Minimal => 1024, + Effort::Low => 2048, + Effort::Medium => 8192, + Effort::High => 16384, + Effort::XHigh => 32768, + }); + // Enable if a non-None effort is set, or if explicitly enabled + let should_enable = effort_budget.is_some() || reasoning.enabled == Some(true); + if should_enable { + let budget_tokens = effort_budget + .or_else(|| reasoning.max_tokens.map(|t| t as u64)) + .unwrap_or(10000); + Some(Thinking { r#type: ThinkingType::Enabled, budget_tokens }) + } else { + None + } }), output_format: request.response_format.and_then(|rf| match rf { forge_domain::ResponseFormat::Text => { diff --git a/crates/forge_app/src/dto/google/request.rs b/crates/forge_app/src/dto/google/request.rs index c148e28414..c1867dcf0e 100644 --- a/crates/forge_app/src/dto/google/request.rs +++ b/crates/forge_app/src/dto/google/request.rs @@ -397,17 +397,33 @@ impl From for Request { _ => None, }), thinking_config: context.reasoning.and_then(|reasoning| { - reasoning.enabled.and_then(|enabled| { - if enabled { - Some(ThinkingConfig { - thinking_level: None, - thinking_budget: reasoning.max_tokens.map(|t| t as i32), - include_thoughts: Some(true), - }) - } else { - None - } - }) + use forge_domain::Effort; + // Effort::None explicitly disables thinking + if matches!(reasoning.effort, Some(Effort::None)) { + return None; + } + // Map effort variant to Google's Level enum + let thinking_level = reasoning.effort.as_ref().and_then(|e| match e { + Effort::None => None, // unreachable — handled above + Effort::Minimal => Some(Level::Minimal), + Effort::Low => Some(Level::Low), + Effort::Medium => Some(Level::Medium), + Effort::High | Effort::XHigh => Some(Level::High), + }); + let thinking_budget = reasoning.max_tokens.map(|t| t as i32); + // Enable if effort is set, budget is set, or explicitly enabled + if thinking_level.is_some() + || thinking_budget.is_some() + || reasoning.enabled == Some(true) + { + Some(ThinkingConfig { + thinking_level, + thinking_budget, + include_thoughts: Some(true), + }) + } else { + None + } }), ..Default::default() }); diff --git a/crates/forge_app/src/services.rs b/crates/forge_app/src/services.rs index c11b4fafe4..45348168e8 100644 --- a/crates/forge_app/src/services.rs +++ b/crates/forge_app/src/services.rs @@ -226,6 +226,17 @@ pub trait AppConfigService: Send + Sync { /// Sets the suggest configuration (provider and model for command /// suggestion generation). async fn set_suggest_config(&self, config: forge_domain::SuggestConfig) -> anyhow::Result<()>; + + /// Gets the global reasoning configuration. + async fn get_reasoning_config( + &self, + ) -> anyhow::Result>; + + /// Sets the global reasoning configuration. + async fn set_reasoning_config( + &self, + config: forge_domain::ReasoningConfig, + ) -> anyhow::Result<()>; } #[async_trait::async_trait] @@ -984,6 +995,19 @@ impl AppConfigService for I { async fn set_suggest_config(&self, config: forge_domain::SuggestConfig) -> anyhow::Result<()> { self.config_service().set_suggest_config(config).await } + + async fn get_reasoning_config( + &self, + ) -> anyhow::Result> { + self.config_service().get_reasoning_config().await + } + + async fn set_reasoning_config( + &self, + config: forge_domain::ReasoningConfig, + ) -> anyhow::Result<()> { + self.config_service().set_reasoning_config(config).await + } } #[async_trait::async_trait] diff --git a/crates/forge_domain/src/env.rs b/crates/forge_domain/src/env.rs index 1953d804f2..87b50ba3d8 100644 --- a/crates/forge_domain/src/env.rs +++ b/crates/forge_domain/src/env.rs @@ -40,6 +40,8 @@ pub enum ConfigOperation { SetCommitConfig(CommitConfig), /// Set the shell-command suggestion configuration. SetSuggestConfig(SuggestConfig), + /// Set the global reasoning configuration. + SetReasoning(ReasoningConfig), } const VERSION: &str = match option_env!("APP_VERSION") { @@ -263,6 +265,9 @@ impl Environment { .model_id(suggest.model.to_string()), ); } + ConfigOperation::SetReasoning(reasoning) => { + self.reasoning = Some(reasoning); + } } } @@ -388,6 +393,7 @@ mod tests { use pretty_assertions::assert_eq; use super::*; + use crate::Effort; fn fixture_env() -> Environment { Faker.fake() @@ -483,6 +489,14 @@ mod tests { assert_eq!(fixture.suggest, Some(expected)); } + #[test] + fn test_apply_op_set_reasoning() { + let mut fixture = fixture_env(); + let reasoning = ReasoningConfig::default().effort(Effort::High); + fixture.apply_op(ConfigOperation::SetReasoning(reasoning.clone())); + assert_eq!(fixture.reasoning, Some(reasoning)); + } + #[test] fn test_agent_cwd_path() { let fixture: Environment = Faker.fake(); diff --git a/crates/forge_main/src/built_in_commands.json b/crates/forge_main/src/built_in_commands.json index b57f5b0dc8..723d002740 100644 --- a/crates/forge_main/src/built_in_commands.json +++ b/crates/forge_main/src/built_in_commands.json @@ -31,6 +31,14 @@ "command": "config-suggest-model", "description": "Set the model used for command suggestion generation [alias: csm]" }, + { + "command": "reasoning", + "description": "Switch the reasoning effort for the current session only, without modifying global config [alias: re]" + }, + { + "command": "config-reasoning", + "description": "Set the global reasoning effort level [alias: cr]" + }, { "command": "config", "description": "List current configuration values" diff --git a/crates/forge_main/src/cli.rs b/crates/forge_main/src/cli.rs index d2189594f6..34a5a195a3 100644 --- a/crates/forge_main/src/cli.rs +++ b/crates/forge_main/src/cli.rs @@ -10,6 +10,37 @@ use std::path::PathBuf; use clap::{Parser, Subcommand, ValueEnum}; use forge_domain::{AgentId, ConversationId, ModelId, ProviderId}; +/// Reasoning effort level for CLI configuration. +#[derive(Copy, Clone, Debug, ValueEnum)] +#[clap(rename_all = "lower")] +pub enum CliEffort { + /// No reasoning tokens; disables extended thinking entirely. + None, + /// Minimal reasoning; fastest, fewest thinking tokens. + Minimal, + /// Constrained reasoning suitable for straightforward tasks. + Low, + /// Balanced reasoning for moderately complex tasks. + Medium, + /// Deep reasoning for complex problems. + High, + /// Maximum reasoning budget for the hardest tasks. + Xhigh, +} + +impl From for forge_domain::Effort { + fn from(e: CliEffort) -> Self { + match e { + CliEffort::None => forge_domain::Effort::None, + CliEffort::Minimal => forge_domain::Effort::Minimal, + CliEffort::Low => forge_domain::Effort::Low, + CliEffort::Medium => forge_domain::Effort::Medium, + CliEffort::High => forge_domain::Effort::High, + CliEffort::Xhigh => forge_domain::Effort::XHigh, + } + } +} + #[derive(Parser)] #[command(version = env!("CARGO_PKG_VERSION"))] pub struct Cli { @@ -542,6 +573,11 @@ pub enum ConfigSetField { /// Model ID to use for command suggestion generation. model: ModelId, }, + /// Set the global reasoning effort level. + Reasoning { + /// Reasoning effort level (none/minimal/low/medium/high/xhigh). + effort: CliEffort, + }, } /// Type-safe subcommands for `forge config get`. @@ -555,6 +591,8 @@ pub enum ConfigGetField { Commit, /// Get the command suggestion generation config. Suggest, + /// Get the global reasoning effort level. + Reasoning, } /// Command group for conversation management. @@ -943,6 +981,38 @@ mod tests { assert_eq!(actual, expected); } + #[test] + fn test_config_set_reasoning_effort() { + let fixture = Cli::parse_from(["forge", "config", "set", "reasoning", "high"]); + let actual = match fixture.subcommands { + Some(TopLevelCommand::Config(config)) => match config.command { + ConfigCommand::Set(args) => match args.field { + ConfigSetField::Reasoning { effort } => { + Some(forge_domain::Effort::from(effort).to_string()) + } + _ => None, + }, + _ => None, + }, + _ => None, + }; + let expected = Some("high".to_string()); + assert_eq!(actual, expected); + } + + #[test] + fn test_config_get_reasoning() { + let fixture = Cli::parse_from(["forge", "config", "get", "reasoning"]); + let actual = match fixture.subcommands { + Some(TopLevelCommand::Config(config)) => match config.command { + ConfigCommand::Get(args) => matches!(args.field, ConfigGetField::Reasoning), + _ => panic!("Expected ConfigCommand::Get"), + }, + _ => panic!("Expected TopLevelCommand::Config"), + }; + assert!(actual); + } + #[test] fn test_conversation_list() { let fixture = Cli::parse_from(["forge", "conversation", "list"]); diff --git a/crates/forge_main/src/ui.rs b/crates/forge_main/src/ui.rs index ec0cd1a38a..4a05ebcec3 100644 --- a/crates/forge_main/src/ui.rs +++ b/crates/forge_main/src/ui.rs @@ -3448,6 +3448,16 @@ impl A + Send + Sync> UI { format!("is now the suggest model for provider '{provider}'"), ))?; } + ConfigSetField::Reasoning { effort } => { + let domain_effort = forge_domain::Effort::from(effort); + let reasoning = + forge_domain::ReasoningConfig::default().effort(domain_effort.clone()); + self.api.set_reasoning_config(reasoning).await?; + self.writeln_title( + TitleFormat::action(domain_effort.to_string().as_str()) + .sub_title("is now the reasoning effort level"), + )?; + } } Ok(()) @@ -3509,6 +3519,19 @@ impl A + Send + Sync> UI { None => self.writeln("Suggest: Not set")?, } } + ConfigGetField::Reasoning => { + let reasoning = self.api.get_reasoning_config().await?; + match reasoning { + Some(config) => { + let effort = config + .effort + .map(|e| e.to_string()) + .unwrap_or_else(|| "Not set".to_string()); + self.writeln(effort)?; + } + None => self.writeln("Reasoning: Not set")?, + } + } } Ok(()) diff --git a/crates/forge_repo/src/provider/bedrock.rs b/crates/forge_repo/src/provider/bedrock.rs index 9e949cbc23..f3c1c6a440 100644 --- a/crates/forge_repo/src/provider/bedrock.rs +++ b/crates/forge_repo/src/provider/bedrock.rs @@ -561,34 +561,46 @@ impl FromDomain // Based on AWS Bedrock docs: additionalModelRequestFields for Claude extended // thinking https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html let additional_model_fields = if let Some(reasoning_config) = &context.reasoning { - if reasoning_config.enabled.unwrap_or(false) { - let mut thinking_config = std::collections::HashMap::new(); - thinking_config.insert( - "type".to_string(), - aws_smithy_types::Document::String("enabled".to_string()), - ); - - // Set budget_tokens (REQUIRED when thinking is enabled) - // The budget_tokens parameter determines the maximum number of tokens - // Claude is allowed to use for its internal reasoning process - // Default to 4000 if not specified (AWS recommendation for good quality) - let budget_tokens = reasoning_config.max_tokens.unwrap_or(4000); - thinking_config.insert( - "budget_tokens".to_string(), - aws_smithy_types::Document::Number(aws_smithy_types::Number::PosInt( - budget_tokens as u64, - )), - ); - - let mut fields = std::collections::HashMap::new(); - fields.insert( - "thinking".to_string(), - aws_smithy_types::Document::Object(thinking_config), - ); - - Some(aws_smithy_types::Document::Object(fields)) - } else { + // Effort::None explicitly disables thinking + if matches!(reasoning_config.effort, Some(forge_domain::Effort::None)) { None + } else { + // Map effort variant to a token budget; takes priority over max_tokens + let effort_budget: Option = + reasoning_config.effort.as_ref().map(|e| match e { + forge_domain::Effort::None => 0, // unreachable — handled above + forge_domain::Effort::Minimal => 1024, + forge_domain::Effort::Low => 2048, + forge_domain::Effort::Medium => 8192, + forge_domain::Effort::High => 16384, + forge_domain::Effort::XHigh => 32768, + }); + let should_enable = + effort_budget.is_some() || reasoning_config.enabled.unwrap_or(false); + if should_enable { + let budget_tokens = effort_budget + .or(reasoning_config.max_tokens) + .unwrap_or(4000); + let mut thinking_config = std::collections::HashMap::new(); + thinking_config.insert( + "type".to_string(), + aws_smithy_types::Document::String("enabled".to_string()), + ); + thinking_config.insert( + "budget_tokens".to_string(), + aws_smithy_types::Document::Number(aws_smithy_types::Number::PosInt( + budget_tokens as u64, + )), + ); + let mut fields = std::collections::HashMap::new(); + fields.insert( + "thinking".to_string(), + aws_smithy_types::Document::Object(thinking_config), + ); + Some(aws_smithy_types::Document::Object(fields)) + } else { + None + } } } else { None diff --git a/crates/forge_services/src/app_config.rs b/crates/forge_services/src/app_config.rs index 1ef9f182a0..e6057ea6a1 100644 --- a/crates/forge_services/src/app_config.rs +++ b/crates/forge_services/src/app_config.rs @@ -2,7 +2,8 @@ use std::sync::Arc; use forge_app::{AppConfigService, EnvironmentInfra}; use forge_domain::{ - CommitConfig, ConfigOperation, ModelId, ProviderId, ProviderRepository, SuggestConfig, + CommitConfig, ConfigOperation, ModelId, ProviderId, ProviderRepository, ReasoningConfig, + SuggestConfig, }; use tracing::debug; @@ -127,6 +128,15 @@ impl AppConfigService self.update(ConfigOperation::SetSuggestConfig(suggest_config)) .await } + + async fn get_reasoning_config(&self) -> anyhow::Result> { + let env = self.infra.get_environment(); + Ok(env.reasoning) + } + + async fn set_reasoning_config(&self, reasoning: ReasoningConfig) -> anyhow::Result<()> { + self.update(ConfigOperation::SetReasoning(reasoning)).await + } } #[cfg(test)] @@ -262,6 +272,9 @@ mod tests { .model_id(suggest.model.to_string()), ); } + ConfigOperation::SetReasoning(reasoning) => { + env.reasoning = Some(reasoning); + } } } Ok(()) diff --git a/shell-plugin/forge.theme.zsh b/shell-plugin/forge.theme.zsh index 065f275a0b..59e4cbaaa1 100644 --- a/shell-plugin/forge.theme.zsh +++ b/shell-plugin/forge.theme.zsh @@ -17,6 +17,7 @@ function _forge_prompt_info() { forge_cmd+=(zsh rprompt) [[ -n "$_FORGE_SESSION_MODEL" ]] && local -x FORGE_SESSION__MODEL_ID="$_FORGE_SESSION_MODEL" [[ -n "$_FORGE_SESSION_PROVIDER" ]] && local -x FORGE_SESSION__PROVIDER_ID="$_FORGE_SESSION_PROVIDER" + [[ -n "$_FORGE_SESSION_REASONING" ]] && local -x FORGE_REASONING__EFFORT="$_FORGE_SESSION_REASONING" _FORGE_CONVERSATION_ID=$_FORGE_CONVERSATION_ID _FORGE_ACTIVE_AGENT=$_FORGE_ACTIVE_AGENT "${forge_cmd[@]}" } diff --git a/shell-plugin/lib/actions/config.zsh b/shell-plugin/lib/actions/config.zsh index a05a331b72..ddc5adae85 100644 --- a/shell-plugin/lib/actions/config.zsh +++ b/shell-plugin/lib/actions/config.zsh @@ -436,3 +436,89 @@ function _forge_action_skill() { echo _forge_exec list skill } + +# Helper: Print the static list of reasoning effort levels. +# Outputs a two-column header + data table (EFFORT DESCRIPTION) for fzf. +function _forge_reasoning_levels() { + printf "EFFORT DESCRIPTION\n" + printf "none No reasoning tokens; disables extended thinking entirely\n" + printf "minimal Minimal reasoning; fastest, fewest thinking tokens\n" + printf "low Constrained reasoning suitable for straightforward tasks\n" + printf "medium Balanced reasoning for moderately complex tasks\n" + printf "high Deep reasoning for complex problems\n" + printf "xhigh Maximum reasoning budget for the hardest tasks\n" +} + +# Action handler: Select reasoning effort level for the current session only. +# Sets _FORGE_SESSION_REASONING in the shell environment so that every +# subsequent forge invocation uses FORGE_REASONING__EFFORT without touching +# the permanent global configuration. +function _forge_action_reasoning() { + local input_text="$1" + echo + + local levels + levels=$(_forge_reasoning_levels) + + local fzf_args=( + --delimiter="$_FORGE_DELIMITER" + --with-nth="1,2" + --prompt="Reasoning ❯ " + ) + + if [[ -n "$input_text" ]]; then + fzf_args+=(--query="$input_text") + fi + + if [[ -n "$_FORGE_SESSION_REASONING" ]]; then + local index=$(_forge_find_index "$levels" "$_FORGE_SESSION_REASONING" 1) + fzf_args+=(--bind="start:pos($index)") + fi + + local selected + selected=$(echo "$levels" | _forge_fzf --header-lines=1 "${fzf_args[@]}") + + if [[ -n "$selected" ]]; then + local effort="${selected%% *}" + _FORGE_SESSION_REASONING="$effort" + _forge_log success "Session reasoning effort set to \033[1m${effort}\033[0m" + fi +} + +# Action handler: Set the global reasoning effort level in the persistent config. +# Calls `forge config set reasoning ` to write to ~/forge/.forge.toml. +function _forge_action_config_reasoning() { + local input_text="$1" + ( + echo + + local levels + levels=$(_forge_reasoning_levels) + + local current_effort + current_effort=$(_forge_exec config get reasoning 2>/dev/null) + + local fzf_args=( + --delimiter="$_FORGE_DELIMITER" + --with-nth="1,2" + --prompt="Config Reasoning ❯ " + ) + + if [[ -n "$input_text" ]]; then + fzf_args+=(--query="$input_text") + fi + + if [[ -n "$current_effort" ]]; then + local index=$(_forge_find_index "$levels" "$current_effort" 1) + fzf_args+=(--bind="start:pos($index)") + fi + + local selected + selected=$(echo "$levels" | _forge_fzf --header-lines=1 "${fzf_args[@]}") + + if [[ -n "$selected" ]]; then + local effort="${selected%% *}" + _forge_exec config set reasoning "$effort" + fi + ) +} diff --git a/shell-plugin/lib/config.zsh b/shell-plugin/lib/config.zsh index 0a7510b321..1a74ed3371 100644 --- a/shell-plugin/lib/config.zsh +++ b/shell-plugin/lib/config.zsh @@ -34,3 +34,8 @@ typeset -h _FORGE_PREVIOUS_CONVERSATION_ID # invocation for the lifetime of the current shell session. typeset -h _FORGE_SESSION_MODEL typeset -h _FORGE_SESSION_PROVIDER + +# Session-scoped reasoning effort override (set via :reasoning / :re). +# When non-empty, FORGE_REASONING__EFFORT is exported to every forge +# invocation for the lifetime of the current shell session. +typeset -h _FORGE_SESSION_REASONING diff --git a/shell-plugin/lib/dispatcher.zsh b/shell-plugin/lib/dispatcher.zsh index e162f28dcf..7aaf4701f8 100644 --- a/shell-plugin/lib/dispatcher.zsh +++ b/shell-plugin/lib/dispatcher.zsh @@ -184,6 +184,12 @@ function forge-accept-line() { config-suggest-model|csm) _forge_action_suggest_model "$input_text" ;; + reasoning|re) + _forge_action_reasoning "$input_text" + ;; + config-reasoning|cr) + _forge_action_config_reasoning "$input_text" + ;; tools|t) _forge_action_tools ;; diff --git a/shell-plugin/lib/helpers.zsh b/shell-plugin/lib/helpers.zsh index 03bcece244..8c0b55c7b8 100644 --- a/shell-plugin/lib/helpers.zsh +++ b/shell-plugin/lib/helpers.zsh @@ -25,6 +25,7 @@ function _forge_exec() { cmd+=("$@") [[ -n "$_FORGE_SESSION_MODEL" ]] && local -x FORGE_SESSION__MODEL_ID="$_FORGE_SESSION_MODEL" [[ -n "$_FORGE_SESSION_PROVIDER" ]] && local -x FORGE_SESSION__PROVIDER_ID="$_FORGE_SESSION_PROVIDER" + [[ -n "$_FORGE_SESSION_REASONING" ]] && local -x FORGE_REASONING__EFFORT="$_FORGE_SESSION_REASONING" "${cmd[@]}" } @@ -41,6 +42,7 @@ function _forge_exec_interactive() { cmd+=("$@") [[ -n "$_FORGE_SESSION_MODEL" ]] && local -x FORGE_SESSION__MODEL_ID="$_FORGE_SESSION_MODEL" [[ -n "$_FORGE_SESSION_PROVIDER" ]] && local -x FORGE_SESSION__PROVIDER_ID="$_FORGE_SESSION_PROVIDER" + [[ -n "$_FORGE_SESSION_REASONING" ]] && local -x FORGE_REASONING__EFFORT="$_FORGE_SESSION_REASONING" "${cmd[@]}" /dev/tty } From 675e0b6028e36f03f179bdaf7fc4e20c7e6644be Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 08:44:19 +0000 Subject: [PATCH 3/3] [autofix.ci] apply automated fixes --- crates/forge_api/src/api.rs | 4 +--- crates/forge_app/src/command_generator.rs | 9 ++------- .../src/dto/openai/transformers/set_reasoning_effort.rs | 3 ++- crates/forge_app/src/services.rs | 8 ++------ crates/forge_config/src/config.rs | 4 +++- crates/forge_domain/src/env.rs | 4 ++-- 6 files changed, 12 insertions(+), 20 deletions(-) diff --git a/crates/forge_api/src/api.rs b/crates/forge_api/src/api.rs index 1a7021e791..83d6c89042 100644 --- a/crates/forge_api/src/api.rs +++ b/crates/forge_api/src/api.rs @@ -149,9 +149,7 @@ pub trait API: Sync + Send { async fn set_suggest_config(&self, config: forge_domain::SuggestConfig) -> anyhow::Result<()>; /// Gets the global reasoning configuration. - async fn get_reasoning_config( - &self, - ) -> anyhow::Result>; + async fn get_reasoning_config(&self) -> anyhow::Result>; /// Sets the global reasoning configuration. async fn set_reasoning_config( diff --git a/crates/forge_app/src/command_generator.rs b/crates/forge_app/src/command_generator.rs index 06d6db1ae7..3493951ab3 100644 --- a/crates/forge_app/src/command_generator.rs +++ b/crates/forge_app/src/command_generator.rs @@ -280,16 +280,11 @@ mod tests { Ok(()) } - async fn get_reasoning_config( - &self, - ) -> Result> { + async fn get_reasoning_config(&self) -> Result> { Ok(None) } - async fn set_reasoning_config( - &self, - _config: forge_domain::ReasoningConfig, - ) -> Result<()> { + async fn set_reasoning_config(&self, _config: forge_domain::ReasoningConfig) -> Result<()> { Ok(()) } } diff --git a/crates/forge_app/src/dto/openai/transformers/set_reasoning_effort.rs b/crates/forge_app/src/dto/openai/transformers/set_reasoning_effort.rs index eec5b1dfb5..65b75ab64a 100644 --- a/crates/forge_app/src/dto/openai/transformers/set_reasoning_effort.rs +++ b/crates/forge_app/src/dto/openai/transformers/set_reasoning_effort.rs @@ -11,7 +11,8 @@ use crate::dto::openai::Request; /// # Transformation Rules /// /// - If `reasoning.enabled == Some(false)` → use "none" (disables reasoning) -/// - If `reasoning.effort` is set (none/minimal/low/medium/high/xhigh) → use that value +/// - If `reasoning.effort` is set (none/minimal/low/medium/high/xhigh) → use +/// that value /// - If `reasoning.max_tokens` is set (thinking budget) → convert to effort: /// - 0 → "none" /// - 1–512 → "minimal" diff --git a/crates/forge_app/src/services.rs b/crates/forge_app/src/services.rs index 45348168e8..4c48c6f280 100644 --- a/crates/forge_app/src/services.rs +++ b/crates/forge_app/src/services.rs @@ -228,9 +228,7 @@ pub trait AppConfigService: Send + Sync { async fn set_suggest_config(&self, config: forge_domain::SuggestConfig) -> anyhow::Result<()>; /// Gets the global reasoning configuration. - async fn get_reasoning_config( - &self, - ) -> anyhow::Result>; + async fn get_reasoning_config(&self) -> anyhow::Result>; /// Sets the global reasoning configuration. async fn set_reasoning_config( @@ -996,9 +994,7 @@ impl AppConfigService for I { self.config_service().set_suggest_config(config).await } - async fn get_reasoning_config( - &self, - ) -> anyhow::Result> { + async fn get_reasoning_config(&self) -> anyhow::Result> { self.config_service().get_reasoning_config().await } diff --git a/crates/forge_config/src/config.rs b/crates/forge_config/src/config.rs index 7bd4982365..9aef41d45d 100644 --- a/crates/forge_config/src/config.rs +++ b/crates/forge_config/src/config.rs @@ -7,7 +7,9 @@ use serde::{Deserialize, Serialize}; use crate::reader::ConfigReader; use crate::writer::ConfigWriter; -use crate::{AutoDumpFormat, Compact, Decimal, HttpConfig, ModelConfig, ReasoningConfig, RetryConfig, Update}; +use crate::{ + AutoDumpFormat, Compact, Decimal, HttpConfig, ModelConfig, ReasoningConfig, RetryConfig, Update, +}; /// Top-level Forge configuration merged from all sources (defaults, file, /// environment). diff --git a/crates/forge_domain/src/env.rs b/crates/forge_domain/src/env.rs index 87b50ba3d8..639c21c4bf 100644 --- a/crates/forge_domain/src/env.rs +++ b/crates/forge_domain/src/env.rs @@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize}; use url::Url; use crate::{ - CommitConfig, Compact, HttpConfig, MaxTokens, ModelId, ProviderId, ReasoningConfig, RetryConfig, - SuggestConfig, Temperature, TopK, TopP, Update, + CommitConfig, Compact, HttpConfig, MaxTokens, ModelId, ProviderId, ReasoningConfig, + RetryConfig, SuggestConfig, Temperature, TopK, TopP, Update, }; /// Domain-level session configuration pairing a provider with a model.