From 18864201d469c3907ebead21dff7fb059f060fcf Mon Sep 17 00:00:00 2001 From: leach Date: Tue, 19 Aug 2025 23:28:17 -0400 Subject: [PATCH] cleaned up presentation --- src/cli.rs | 65 ++++++++++++++++++++++++++++++++++++++++++-- src/core/client.rs | 50 +++++++++++++++++++++++++++++++--- src/core/session.rs | 34 +++++++++++++++++++++-- src/utils/display.rs | 11 ++++++-- 4 files changed, 148 insertions(+), 12 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index dacfe57..615400d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -67,6 +67,7 @@ impl ChatCLI { if let Err(e) = self.handle_user_message(line).await { self.display.print_error(&format!("Error: {}", e)); } + println!(); // Add padding before next prompt } } None => { @@ -91,6 +92,8 @@ impl ChatCLI { let enable_web_search = self.session.enable_web_search; let enable_reasoning_summary = self.session.enable_reasoning_summary; let reasoning_effort = self.session.reasoning_effort.clone(); + let enable_extended_thinking = self.session.enable_extended_thinking; + let thinking_budget_tokens = self.session.thinking_budget_tokens; // Check if we should use streaming before getting client let should_use_streaming = { @@ -99,7 +102,8 @@ impl ChatCLI { }; if should_use_streaming { - print!("{} ", console::style("🤖").magenta()); + println!(); // Add padding before AI response + print!("{}> ", console::style("🤖").magenta()); use std::io::{self, Write}; io::stdout().flush().ok(); @@ -121,6 +125,8 @@ impl ChatCLI { enable_web_search, enable_reasoning_summary, &reasoning_effort, + enable_extended_thinking, + thinking_budget_tokens, stream_callback, ) .await @@ -148,6 +154,8 @@ impl ChatCLI { enable_web_search, enable_reasoning_summary, &reasoning_effort, + enable_extended_thinking, + thinking_budget_tokens, ) .await { @@ -379,15 +387,17 @@ impl ChatCLI { let web_status = if self.session.enable_web_search { "✓ enabled" } else { "✗ disabled" }; let reasoning_status = if self.session.enable_reasoning_summary { "✓ enabled" } else { "✗ disabled" }; + let extended_thinking_status = if self.session.enable_extended_thinking { "✓ enabled" } else { "✗ disabled" }; println!(" Web Search: {}", web_status); println!(" Reasoning Summaries: {}", reasoning_status); println!(" Reasoning Effort: {}", self.session.reasoning_effort); + println!(" Extended Thinking: {}", extended_thinking_status); + println!(" Thinking Budget: {} tokens", self.session.thinking_budget_tokens); // Check model compatibility let model = self.session.model.clone(); let provider = get_provider_for_model(&model); - let web_enabled = self.session.enable_web_search; let reasoning_enabled = self.session.enable_reasoning_summary; // Show compatibility warnings based on provider @@ -396,10 +406,16 @@ impl ChatCLI { if reasoning_enabled { self.display.print_warning("Reasoning summaries are not supported by Anthropic models"); } + if self.session.enable_extended_thinking { + // Extended thinking is supported by Anthropic models + } // Web search is now supported by Anthropic models } crate::core::provider::Provider::OpenAI => { - // OpenAI models generally support these features + if self.session.enable_extended_thinking { + self.display.print_warning("Extended thinking is not supported by OpenAI models"); + } + // OpenAI models generally support other features } } @@ -408,6 +424,8 @@ impl ChatCLI { "Toggle Web Search", "Toggle Reasoning Summaries", "Set Reasoning Effort", + "Toggle Extended Thinking", + "Set Thinking Budget", "Done" ]; @@ -443,6 +461,47 @@ impl ChatCLI { } } } + Some("Toggle Extended Thinking") => { + self.session.enable_extended_thinking = !self.session.enable_extended_thinking; + let state = if self.session.enable_extended_thinking { "enabled" } else { "disabled" }; + self.display.print_command_result(&format!("Extended thinking {}", state)); + + let provider = get_provider_for_model(&self.session.model); + match provider { + crate::core::provider::Provider::OpenAI => { + self.display.print_warning("Extended thinking is not supported by OpenAI models"); + } + crate::core::provider::Provider::Anthropic => { + // Supported + } + } + } + Some("Set Thinking Budget") => { + let budget_options = vec!["1024", "2500", "5000", "10000", "16000"]; + let current_budget = self.session.thinking_budget_tokens.to_string(); + if let Some(budget_str) = self.input.select_from_list( + "Select thinking budget (tokens):", + &budget_options, + Some(¤t_budget), + )? { + if let Ok(budget) = budget_str.parse::() { + self.session.thinking_budget_tokens = budget; + self.display.print_command_result(&format!("Thinking budget set to {} tokens", budget)); + + let provider = get_provider_for_model(&self.session.model); + match provider { + crate::core::provider::Provider::OpenAI => { + self.display.print_warning("Extended thinking is not supported by OpenAI models"); + } + crate::core::provider::Provider::Anthropic => { + if budget < 1024 { + self.display.print_warning("Minimum thinking budget is 1024 tokens for Anthropic models"); + } + } + } + } + } + } Some("Done") | None => { break; } diff --git a/src/core/client.rs b/src/core/client.rs index cdc9a64..59cdf23 100644 --- a/src/core/client.rs +++ b/src/core/client.rs @@ -27,13 +27,15 @@ impl ChatClient { enable_web_search: bool, enable_reasoning_summary: bool, reasoning_effort: &str, + enable_extended_thinking: bool, + thinking_budget_tokens: u32, ) -> Result { match self { ChatClient::OpenAI(client) => { client.chat_completion(model, messages, enable_web_search, enable_reasoning_summary, reasoning_effort).await } ChatClient::Anthropic(client) => { - client.chat_completion(model, messages, enable_web_search, enable_reasoning_summary, reasoning_effort).await + client.chat_completion(model, messages, enable_web_search, enable_reasoning_summary, reasoning_effort, enable_extended_thinking, thinking_budget_tokens).await } } } @@ -45,6 +47,8 @@ impl ChatClient { enable_web_search: bool, enable_reasoning_summary: bool, reasoning_effort: &str, + enable_extended_thinking: bool, + thinking_budget_tokens: u32, stream_callback: StreamCallback, ) -> Result { match self { @@ -52,7 +56,7 @@ impl ChatClient { client.chat_completion_stream(model, messages, enable_web_search, enable_reasoning_summary, reasoning_effort, stream_callback).await } ChatClient::Anthropic(client) => { - client.chat_completion_stream(model, messages, enable_web_search, enable_reasoning_summary, reasoning_effort, stream_callback).await + client.chat_completion_stream(model, messages, enable_web_search, enable_reasoning_summary, reasoning_effort, enable_extended_thinking, thinking_budget_tokens, stream_callback).await } } } @@ -900,15 +904,26 @@ impl AnthropicClient { _enable_web_search: bool, _enable_reasoning_summary: bool, _reasoning_effort: &str, + enable_extended_thinking: bool, + thinking_budget_tokens: u32, ) -> Result { let url = format!("{}/messages", self.base_url); let (system_prompt, user_messages) = Self::convert_messages(messages); let config = crate::config::Config::load().unwrap_or_default(); + + // Calculate max_tokens - must be greater than thinking budget when extended thinking is enabled + let max_tokens = if enable_extended_thinking { + // Ensure max_tokens is at least thinking_budget + 4096 for the actual response + std::cmp::max(config.limits.max_tokens_anthropic, thinking_budget_tokens + 4096) + } else { + config.limits.max_tokens_anthropic + }; + let mut payload = json!({ "model": model, - "max_tokens": config.limits.max_tokens_anthropic, + "max_tokens": max_tokens, "messages": user_messages }); @@ -924,6 +939,14 @@ impl AnthropicClient { }]); } + // Add extended thinking if enabled + if enable_extended_thinking { + payload["thinking"] = json!({ + "type": "enabled", + "budget_tokens": thinking_budget_tokens + }); + } + let response = self .client .post(&url) @@ -961,6 +984,8 @@ impl AnthropicClient { _enable_web_search: bool, _enable_reasoning_summary: bool, _reasoning_effort: &str, + enable_extended_thinking: bool, + thinking_budget_tokens: u32, stream_callback: StreamCallback, ) -> Result { let url = format!("{}/messages", self.base_url); @@ -968,9 +993,18 @@ impl AnthropicClient { let (system_prompt, user_messages) = Self::convert_messages(messages); let config = crate::config::Config::load().unwrap_or_default(); + + // Calculate max_tokens - must be greater than thinking budget when extended thinking is enabled + let max_tokens = if enable_extended_thinking { + // Ensure max_tokens is at least thinking_budget + 4096 for the actual response + std::cmp::max(config.limits.max_tokens_anthropic, thinking_budget_tokens + 4096) + } else { + config.limits.max_tokens_anthropic + }; + let mut payload = json!({ "model": model, - "max_tokens": config.limits.max_tokens_anthropic, + "max_tokens": max_tokens, "messages": user_messages, "stream": true }); @@ -987,6 +1021,14 @@ impl AnthropicClient { }]); } + // Add extended thinking if enabled + if enable_extended_thinking { + payload["thinking"] = json!({ + "type": "enabled", + "budget_tokens": thinking_budget_tokens + }); + } + let response = self .client .post(&url) diff --git a/src/core/session.rs b/src/core/session.rs index d425957..b939cc7 100644 --- a/src/core/session.rs +++ b/src/core/session.rs @@ -44,6 +44,10 @@ pub struct SessionData { pub enable_reasoning_summary: bool, #[serde(default = "default_reasoning_effort")] pub reasoning_effort: String, + #[serde(default = "default_enable_extended_thinking")] + pub enable_extended_thinking: bool, + #[serde(default = "default_thinking_budget")] + pub thinking_budget_tokens: u32, pub updated_at: DateTime, } @@ -51,6 +55,14 @@ fn default_reasoning_effort() -> String { "medium".to_string() } +fn default_enable_extended_thinking() -> bool { + false +} + +fn default_thinking_budget() -> u32 { + 5000 +} + #[derive(Debug, Clone)] pub struct Session { pub name: String, @@ -59,6 +71,8 @@ pub struct Session { pub enable_web_search: bool, pub enable_reasoning_summary: bool, pub reasoning_effort: String, + pub enable_extended_thinking: bool, + pub thinking_budget_tokens: u32, } impl Session { @@ -70,6 +84,8 @@ impl Session { enable_web_search: true, enable_reasoning_summary: false, reasoning_effort: "medium".to_string(), + enable_extended_thinking: false, + thinking_budget_tokens: 5000, }; // Add system prompt as first message @@ -106,6 +122,8 @@ impl Session { enable_web_search: self.enable_web_search, enable_reasoning_summary: self.enable_reasoning_summary, reasoning_effort: self.reasoning_effort.clone(), + enable_extended_thinking: self.enable_extended_thinking, + thinking_budget_tokens: self.thinking_budget_tokens, updated_at: Utc::now(), }; @@ -144,6 +162,8 @@ impl Session { enable_web_search: data.enable_web_search, enable_reasoning_summary: data.enable_reasoning_summary, reasoning_effort: data.reasoning_effort, + enable_extended_thinking: data.enable_extended_thinking, + thinking_budget_tokens: data.thinking_budget_tokens, }; // Ensure system prompt is present @@ -386,6 +406,8 @@ impl Session { enable_web_search: self.enable_web_search, enable_reasoning_summary: self.enable_reasoning_summary, reasoning_effort: self.reasoning_effort.clone(), + enable_extended_thinking: self.enable_extended_thinking, + thinking_budget_tokens: self.thinking_budget_tokens, }; // Save the new session @@ -415,7 +437,9 @@ impl Session { content.push_str(&format!("**Model:** {}\n", self.model)); content.push_str(&format!("**Web Search:** {}\n", if self.enable_web_search { "Enabled" } else { "Disabled" })); content.push_str(&format!("**Reasoning Summary:** {}\n", if self.enable_reasoning_summary { "Enabled" } else { "Disabled" })); - content.push_str(&format!("**Reasoning Effort:** {}\n\n", self.reasoning_effort)); + content.push_str(&format!("**Reasoning Effort:** {}\n", self.reasoning_effort)); + content.push_str(&format!("**Extended Thinking:** {}\n", if self.enable_extended_thinking { "Enabled" } else { "Disabled" })); + content.push_str(&format!("**Thinking Budget:** {} tokens\n\n", self.thinking_budget_tokens)); content.push_str("---\n\n"); // Messages (skip system prompt) @@ -445,7 +469,9 @@ impl Session { "settings": { "enable_web_search": self.enable_web_search, "enable_reasoning_summary": self.enable_reasoning_summary, - "reasoning_effort": self.reasoning_effort + "reasoning_effort": self.reasoning_effort, + "enable_extended_thinking": self.enable_extended_thinking, + "thinking_budget_tokens": self.thinking_budget_tokens }, "messages": self.messages, "exported_at": chrono::Utc::now().to_rfc3339() @@ -463,7 +489,9 @@ impl Session { content.push_str(&format!("Model: {}\n", self.model)); content.push_str(&format!("Web Search: {}\n", if self.enable_web_search { "Enabled" } else { "Disabled" })); content.push_str(&format!("Reasoning Summary: {}\n", if self.enable_reasoning_summary { "Enabled" } else { "Disabled" })); - content.push_str(&format!("Reasoning Effort: {}\n\n", self.reasoning_effort)); + content.push_str(&format!("Reasoning Effort: {}\n", self.reasoning_effort)); + content.push_str(&format!("Extended Thinking: {}\n", if self.enable_extended_thinking { "Enabled" } else { "Disabled" })); + content.push_str(&format!("Thinking Budget: {} tokens\n\n", self.thinking_budget_tokens)); content.push_str("===============================================\n\n"); // Messages (skip system prompt) diff --git a/src/utils/display.rs b/src/utils/display.rs index 99d4466..af85d36 100644 --- a/src/utils/display.rs +++ b/src/utils/display.rs @@ -46,11 +46,13 @@ impl Display { #[allow(dead_code)] pub fn print_user_input(&self, content: &str) { - println!("{} {}", style("👤").cyan(), content); + println!("{}> {}", style("👤").cyan(), content); + println!(); // Add single line of padding after user input } pub fn print_assistant_response(&self, content: &str) { - print!("{} ", style("🤖").magenta()); + println!(); // Add padding before AI response + print!("{}> ", style("🤖").magenta()); self.print_formatted_content_with_pagination(content); } @@ -138,6 +140,11 @@ impl Display { // Print remaining text print!("{}", &text[last_end..]); + + // Ensure content ends with a newline if it doesn't already + if !text.ends_with('\n') { + println!(); + } } fn print_code_block(&self, code: &str, language: &str) {