use crate::error::{AiError, AiResult}; pub const DEFAULT_SYSTEM_PROMPT: &str = r#"You are a precise autonomous agent that executes tasks through tool calls. ## Core Principles - Use tools when they can materially improve correctness or efficiency - After each action, verify results and adjust approach if needed - Keep reasoning concise and focus on actionable outcomes - Return only the final useful answer to the user ## Workflow 1. Analyze the request and plan your approach 2. Execute actions using appropriate tools 3. Review observations and verify assumptions 4. Iterate until the task is complete 5. Provide a clear, concise final response ## Title Generation If this is the first user message in a new conversation with a default title, you SHOULD call `set_conversation_title` as your first action to create a short, descriptive title (max 100 chars). This helps keep the conversation organized. Only do this once at the very beginning."#; #[derive(Clone, Debug)] pub struct AgentConfig { pub model: String, pub provider: String, pub api_mode: String, pub system_prompt: String, pub max_iterations: usize, pub iteration_budget: usize, pub temperature: Option, pub max_completion_tokens: Option, pub max_total_tokens_per_run: Option, pub enabled_toolsets: Vec, pub disabled_toolsets: Vec, pub allowed_tools: Vec, pub denied_tools: Vec, pub retry_max_attempts: usize, pub retry_base_delay_ms: u64, pub retry_jitter: bool, pub fallback_model: Option, pub skip_memory: bool, pub skip_context_files: bool, pub skip_compression: bool, pub quiet_mode: bool, pub save_trajectories: bool, pub reasoning_effort: Option, pub service_tier: Option, pub platform: Option, pub session_id: Option, } impl AgentConfig { pub fn new(model: impl Into) -> AiResult { let config = Self { model: model.into(), provider: String::new(), api_mode: String::from("chat_completions"), system_prompt: DEFAULT_SYSTEM_PROMPT.to_string(), max_iterations: 64, iteration_budget: 90, temperature: Some(0.2), max_completion_tokens: None, max_total_tokens_per_run: Some(128_000), enabled_toolsets: Vec::new(), disabled_toolsets: Vec::new(), allowed_tools: Vec::new(), denied_tools: Vec::new(), retry_max_attempts: 3, retry_base_delay_ms: 1_000, retry_jitter: true, fallback_model: None, skip_memory: false, skip_context_files: false, skip_compression: false, quiet_mode: false, save_trajectories: false, reasoning_effort: None, service_tier: None, platform: None, session_id: None, }; config.validate()?; Ok(config) } pub fn validate(&self) -> AiResult<()> { if self.model.trim().is_empty() { return Err(AiError::Config("agent model is required".to_string())); } if self.max_iterations == 0 { return Err(AiError::Config( "agent max_iterations must be greater than 0".to_string(), )); } if let Some(tokens) = self.max_total_tokens_per_run && tokens <= 0 { return Err(AiError::Config( "agent max_total_tokens_per_run must be > 0".to_string(), )); } Ok(()) } pub fn with_provider(mut self, provider: impl Into) -> Self { self.provider = provider.into(); self } pub fn with_api_mode(mut self, mode: impl Into) -> Self { self.api_mode = mode.into(); self } pub fn with_max_iterations(mut self, max: usize) -> Self { self.max_iterations = max; self.iteration_budget = self.iteration_budget.max(max); self } pub fn with_iteration_budget(mut self, budget: usize) -> Self { self.iteration_budget = budget; self } pub fn with_system_prompt(mut self, prompt: impl Into) -> Self { self.system_prompt = prompt.into(); self } pub fn with_temperature(mut self, temperature: Option) -> Self { self.temperature = temperature; self } pub fn with_max_completion_tokens( mut self, max_completion_tokens: Option, ) -> Self { self.max_completion_tokens = max_completion_tokens; self } pub fn with_max_total_tokens(mut self, limit: Option) -> Self { self.max_total_tokens_per_run = limit; self } pub fn with_toolset_policy( mut self, enabled: Vec, disabled: Vec, ) -> Self { self.enabled_toolsets = enabled; self.disabled_toolsets = disabled; self } pub fn with_tool_policy( mut self, allowed_tools: Vec, denied_tools: Vec, ) -> Self { self.allowed_tools = allowed_tools; self.denied_tools = denied_tools; self } pub fn with_retry( mut self, max_attempts: usize, base_delay_ms: u64, ) -> Self { self.retry_max_attempts = max_attempts; self.retry_base_delay_ms = base_delay_ms; self } pub fn with_retry_jitter(mut self, jitter: bool) -> Self { self.retry_jitter = jitter; self } pub fn with_fallback_model( mut self, fallback_model: impl Into, ) -> Self { self.fallback_model = Some(fallback_model.into()); self } pub fn with_skip_memory(mut self, skip: bool) -> Self { self.skip_memory = skip; self } pub fn with_skip_compression(mut self, skip: bool) -> Self { self.skip_compression = skip; self } pub fn with_quiet_mode(mut self, quiet: bool) -> Self { self.quiet_mode = quiet; self } pub fn with_platform(mut self, platform: impl Into) -> Self { self.platform = Some(platform.into()); self } pub fn with_session_id(mut self, session_id: uuid::Uuid) -> Self { self.session_id = Some(session_id); self } pub fn with_reasoning_effort(mut self, effort: impl Into) -> Self { self.reasoning_effort = Some(effort.into()); self } pub fn is_tool_exposed(&self, name: &str) -> bool { let denied = self.denied_tools.iter().any(|tool| tool == name); if denied { return false; } if self.allowed_tools.is_empty() { return true; } self.allowed_tools.iter().any(|tool| tool == name) } } pub fn default_system_prompt() -> &'static str { DEFAULT_SYSTEM_PROMPT }