From 61e7bea47a0ce865908c9a0a50bef15f0b063b56 Mon Sep 17 00:00:00 2001 From: Deanfluence Bot Date: Thu, 12 Mar 2026 11:03:33 +0800 Subject: [PATCH 1/5] feat: wire openai reasoning effort to responses api --- src/llm/model.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/llm/model.rs b/src/llm/model.rs index 9dadcd93f..dc60e1625 100644 --- a/src/llm/model.rs +++ b/src/llm/model.rs @@ -745,6 +745,15 @@ impl SpacebotModel { "input": input, }); + let configured_effort = self + .routing + .as_ref() + .map(|r| r.thinking_effort_for_model(&self.model_name)) + .unwrap_or("auto"); + if let Some(effort) = openai_reasoning_effort(&api_model_name, configured_effort) { + body["reasoning"] = serde_json::json!({ "effort": effort }); + } + if let Some(preamble) = &request.preamble { body["instructions"] = serde_json::json!(preamble); } else if is_chatgpt_codex { @@ -2871,6 +2880,22 @@ fn remap_model_name_for_api(provider: &str, model_name: &str) -> String { } } +fn openai_reasoning_effort<'a>(model_name: &str, configured_effort: &'a str) -> Option<&'a str> { + if configured_effort == "auto" || !model_name.starts_with("gpt-5") { + return None; + } + + let is_pro_model = model_name.contains("-pro"); + match configured_effort { + "max" => Some("xhigh"), + "high" => Some("high"), + "medium" => Some("medium"), + "low" if is_pro_model => Some("medium"), + "low" => Some("low"), + _ => None, + } +} + #[cfg(test)] mod tests { use super::*; @@ -2933,6 +2958,29 @@ mod tests { assert_eq!(remap_model_name_for_api("openai", "zai/glm-5"), "zai/glm-5"); } + #[test] + fn openai_reasoning_effort_omits_auto_and_non_gpt5_models() { + assert_eq!(openai_reasoning_effort("gpt-5.4", "auto"), None); + assert_eq!(openai_reasoning_effort("gpt-4.1", "high"), None); + } + + #[test] + fn openai_reasoning_effort_maps_standard_gpt5_values() { + assert_eq!(openai_reasoning_effort("gpt-5.4", "low"), Some("low")); + assert_eq!(openai_reasoning_effort("gpt-5.4", "medium"), Some("medium")); + assert_eq!(openai_reasoning_effort("gpt-5.4", "high"), Some("high")); + assert_eq!(openai_reasoning_effort("gpt-5.4", "max"), Some("xhigh")); + } + + #[test] + fn openai_reasoning_effort_raises_low_floor_for_pro_models() { + assert_eq!( + openai_reasoning_effort("gpt-5.4-pro", "low"), + Some("medium") + ); + assert_eq!(openai_reasoning_effort("gpt-5.4-pro", "max"), Some("xhigh")); + } + #[test] fn parse_anthropic_response_drops_empty_text_blocks() { let body = serde_json::json!({ From a9fd2d7051ccbffaa7853f5b5c95d4a10263986d Mon Sep 17 00:00:00 2001 From: Deanfluence Bot Date: Thu, 12 Mar 2026 11:10:46 +0800 Subject: [PATCH 2/5] fix: collapse signal routing conditionals --- src/tools/send_message_to_another_channel.rs | 54 +++++++++----------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/src/tools/send_message_to_another_channel.rs b/src/tools/send_message_to_another_channel.rs index abf72e239..bb0681fab 100644 --- a/src/tools/send_message_to_another_channel.rs +++ b/src/tools/send_message_to_another_channel.rs @@ -150,14 +150,13 @@ impl Tool for SendMessageTool { // If explicit prefix returned default "signal" adapter but we're in a named // Signal adapter conversation (e.g., signal:gvoice1), use the current adapter // to ensure the message goes through the correct account. - if target.adapter == "signal" { - if let Some(current_adapter) = self + if target.adapter == "signal" + && let Some(current_adapter) = self .current_adapter .as_ref() .filter(|adapter| adapter.starts_with("signal:")) - { - target.adapter = current_adapter.clone(); - } + { + target.adapter = current_adapter.clone(); } self.messaging_manager @@ -189,31 +188,28 @@ impl Tool for SendMessageTool { .current_adapter .as_ref() .filter(|adapter| adapter.starts_with("signal")) + && let Some(target) = parse_implicit_signal_shorthand(&args.target, current_adapter) { - if let Some(target) = parse_implicit_signal_shorthand(&args.target, current_adapter) { - self.messaging_manager - .broadcast( - &target.adapter, - &target.target, - crate::OutboundResponse::Text(args.message), - ) - .await - .map_err(|error| { - SendMessageError(format!("failed to send message: {error}")) - })?; - - tracing::info!( - adapter = %target.adapter, - broadcast_target = %"[REDACTED]", - "message sent via implicit Signal shorthand" - ); - - return Ok(SendMessageOutput { - success: true, - target: target.target, - platform: target.adapter, - }); - } + self.messaging_manager + .broadcast( + &target.adapter, + &target.target, + crate::OutboundResponse::Text(args.message), + ) + .await + .map_err(|error| SendMessageError(format!("failed to send message: {error}")))?; + + tracing::info!( + adapter = %target.adapter, + broadcast_target = %"[REDACTED]", + "message sent via implicit Signal shorthand" + ); + + return Ok(SendMessageOutput { + success: true, + target: target.target, + platform: target.adapter, + }); } // Check for explicit email target From 66b15ba3c5016ead521276bdabbdc2ef8e5e70b8 Mon Sep 17 00:00:00 2001 From: Deanfluence Bot Date: Thu, 12 Mar 2026 12:08:37 +0800 Subject: [PATCH 3/5] style: tighten reasoning effort helper signature --- src/llm/model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llm/model.rs b/src/llm/model.rs index dc60e1625..58745a303 100644 --- a/src/llm/model.rs +++ b/src/llm/model.rs @@ -2880,7 +2880,7 @@ fn remap_model_name_for_api(provider: &str, model_name: &str) -> String { } } -fn openai_reasoning_effort<'a>(model_name: &str, configured_effort: &'a str) -> Option<&'a str> { +fn openai_reasoning_effort(model_name: &str, configured_effort: &str) -> Option<&'static str> { if configured_effort == "auto" || !model_name.starts_with("gpt-5") { return None; } From 2bb68ec0a4a0ae68d211b091721eaf5099b087ed Mon Sep 17 00:00:00 2001 From: Deanfluence Bot Date: Thu, 12 Mar 2026 12:20:32 +0800 Subject: [PATCH 4/5] fix: preserve thinking effort across fallback retries --- src/llm/model.rs | 53 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/src/llm/model.rs b/src/llm/model.rs index 58745a303..3c17157a2 100644 --- a/src/llm/model.rs +++ b/src/llm/model.rs @@ -50,6 +50,7 @@ pub struct SpacebotModel { provider: String, full_model_name: String, routing: Option, + configured_thinking_effort: Option, agent_id: Option, process_type: Option, worker_type: Option, @@ -72,6 +73,14 @@ impl SpacebotModel { self } + pub fn with_configured_thinking_effort( + mut self, + configured_thinking_effort: impl Into, + ) -> Self { + self.configured_thinking_effort = Some(configured_thinking_effort.into()); + self + } + /// Attach agent context for per-agent metric labels. pub fn with_context( mut self, @@ -89,6 +98,17 @@ impl SpacebotModel { self } + fn configured_thinking_effort(&self) -> &str { + self.configured_thinking_effort + .as_deref() + .or_else(|| { + self.routing + .as_ref() + .map(|routing| routing.thinking_effort_for_model(&self.model_name)) + }) + .unwrap_or("auto") + } + async fn provider_config_for_current_model(&self) -> Result { let provider_id = self .full_model_name @@ -187,11 +207,14 @@ impl SpacebotModel { &self, model_name: &str, request: &CompletionRequest, + configured_effort: &str, ) -> Result, (CompletionError, bool)> { let model = if model_name == self.full_model_name { self.clone() + .with_configured_thinking_effort(configured_effort.to_string()) } else { SpacebotModel::make(&self.llm_manager, model_name) + .with_configured_thinking_effort(configured_effort.to_string()) }; let mut last_error = None; @@ -263,6 +286,7 @@ impl CompletionModel for SpacebotModel { provider, full_model_name, routing: None, + configured_thinking_effort: None, agent_id: None, process_type: None, worker_type: None, @@ -282,6 +306,7 @@ impl CompletionModel for SpacebotModel { return self.attempt_completion(request).await; }; + let configured_effort = routing.thinking_effort_for_model(&self.model_name).to_string(); let cooldown = routing.rate_limit_cooldown_secs; let fallbacks = routing.get_fallbacks(&self.full_model_name); let mut last_error: Option = None; @@ -302,7 +327,7 @@ impl CompletionModel for SpacebotModel { ); } else { match self - .attempt_with_retries(&self.full_model_name, &request) + .attempt_with_retries(&self.full_model_name, &request, &configured_effort) .await { Ok(response) => return Ok(response), @@ -339,7 +364,10 @@ impl CompletionModel for SpacebotModel { continue; } - match self.attempt_with_retries(fallback_name, &request).await { + match self + .attempt_with_retries(fallback_name, &request, &configured_effort) + .await + { Ok(response) => { tracing::info!( original = %self.full_model_name, @@ -552,11 +580,7 @@ impl SpacebotModel { ) -> Result, CompletionError> { let api_key = provider_config.api_key.as_str(); - let effort = self - .routing - .as_ref() - .map(|r| r.thinking_effort_for_model(&self.model_name)) - .unwrap_or("auto"); + let effort = self.configured_thinking_effort(); let anthropic_request = crate::llm::anthropic::build_anthropic_request( self.llm_manager.http_client(), api_key, @@ -745,11 +769,7 @@ impl SpacebotModel { "input": input, }); - let configured_effort = self - .routing - .as_ref() - .map(|r| r.thinking_effort_for_model(&self.model_name)) - .unwrap_or("auto"); + let configured_effort = self.configured_thinking_effort(); if let Some(effort) = openai_reasoning_effort(&api_model_name, configured_effort) { body["reasoning"] = serde_json::json!({ "effort": effort }); } @@ -2892,7 +2912,14 @@ fn openai_reasoning_effort(model_name: &str, configured_effort: &str) -> Option< "medium" => Some("medium"), "low" if is_pro_model => Some("medium"), "low" => Some("low"), - _ => None, + _ => { + tracing::warn!( + model = %model_name, + configured_effort = %configured_effort, + "ignoring invalid OpenAI reasoning effort" + ); + None + } } } From 65f2cfd3559dd2d40a0d988e99de2cd0e69ff00e Mon Sep 17 00:00:00 2001 From: James Pine Date: Thu, 12 Mar 2026 02:29:29 -0700 Subject: [PATCH 5/5] style: fix line length for cargo fmt --- src/llm/model.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/llm/model.rs b/src/llm/model.rs index 3c17157a2..f23690dad 100644 --- a/src/llm/model.rs +++ b/src/llm/model.rs @@ -306,7 +306,9 @@ impl CompletionModel for SpacebotModel { return self.attempt_completion(request).await; }; - let configured_effort = routing.thinking_effort_for_model(&self.model_name).to_string(); + let configured_effort = routing + .thinking_effort_for_model(&self.model_name) + .to_string(); let cooldown = routing.rate_limit_cooldown_secs; let fallbacks = routing.get_fallbacks(&self.full_model_name); let mut last_error: Option = None;