Skip to main content
Glama

CodeGraph CLI MCP Server

by Jakedismo
executor.rs•13.6 kB
// ABOUTME: High-level executor wrapper for AutoAgents workflows // ABOUTME: Orchestrates architecture detection, factory-based executor creation, and delegation use crate::autoagents::codegraph_agent::CodeGraphAgentOutput; use crate::autoagents::startup_context::{build_startup_context, StartupContextRender}; use codegraph_ai::llm_provider::LLMProvider; use codegraph_mcp_core::analysis::AnalysisType; use codegraph_mcp_tools::GraphToolExecutor; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; use thiserror::Error; /// Default execution timeout in seconds (10 minutes) const DEFAULT_TIMEOUT_SECS: u64 = 600; /// Patterns that indicate context window overflow from various LLM providers const CONTEXT_OVERFLOW_PATTERNS: &[&str] = &[ "context_length_exceeded", "maximum context length", "too many tokens", "context window", "token limit", "max_tokens", "context length", "exceeds the maximum", ]; /// Error type for executor operations #[derive(Debug, Error)] pub enum ExecutorError { #[error("Agent build failed: {0}")] BuildFailed(String), #[error("Agent execution failed: {0}")] ExecutionFailed(String), #[error("Tier detection failed: {0}")] TierDetectionFailed(String), #[error("Output conversion failed: {0}")] OutputConversionFailed(String), #[error("Agent execution timed out after {elapsed_secs} seconds")] Timeout { elapsed_secs: u64, partial_result: Option<String>, steps_completed: usize, }, #[error("Context window limit reached: {0}")] ContextOverflow(String), } /// Check if an error message indicates context window overflow pub fn is_context_overflow_error(error_message: &str) -> bool { let lower = error_message.to_lowercase(); CONTEXT_OVERFLOW_PATTERNS .iter() .any(|pattern| lower.contains(pattern)) } /// Transform an ExecutorError to ContextOverflow if it matches overflow patterns pub fn transform_context_overflow(err: ExecutorError) -> ExecutorError { match &err { ExecutorError::ExecutionFailed(msg) if is_context_overflow_error(msg) => { // Log the overflow event for debugging if std::env::var("CODEGRAPH_DEBUG").is_ok() { tracing::warn!( target: "context_overflow", original_error = %msg, "Context window overflow detected" ); } ExecutorError::ContextOverflow( "Context window limit reached. Query too complex for model's capacity.".to_string(), ) } _ => err, } } /// Read timeout configuration from environment variable fn read_timeout_config() -> Duration { std::env::var("CODEGRAPH_AGENT_TIMEOUT_SECS") .ok() .and_then(|s| s.parse::<u64>().ok()) .map(|secs| { if secs == 0 { // Zero means unlimited - use a very large duration Duration::from_secs(u64::MAX / 2) } else { Duration::from_secs(secs) } }) .unwrap_or(Duration::from_secs(DEFAULT_TIMEOUT_SECS)) } /// High-level executor for AutoAgents-based code analysis /// /// This orchestrates the complete workflow: /// 1. Detect agent architecture from configuration /// 2. Use factory to create architecture-specific executor /// 3. Delegate execution to the executor with timeout enforcement /// 4. Return structured output pub struct CodeGraphExecutor { factory: crate::autoagents::executor_factory::AgentExecutorFactory, architecture: codegraph_mcp_core::agent_architecture::AgentArchitecture, timeout: Duration, } impl CodeGraphExecutor { pub fn new( llm_provider: Arc<dyn LLMProvider>, tool_executor: Arc<GraphToolExecutor>, config: Arc<codegraph_mcp_core::config_manager::CodeGraphConfig>, ) -> Self { use crate::autoagents::executor_factory::AgentExecutorFactory; // Create factory for architecture-specific executors let factory = AgentExecutorFactory::new(llm_provider, tool_executor, config.clone()); // Detect architecture from environment or config let architecture = AgentExecutorFactory::detect_architecture(); // Read timeout configuration let timeout = read_timeout_config(); tracing::debug!( timeout_secs = timeout.as_secs(), architecture = %architecture, "CodeGraphExecutor initialized" ); Self { factory, architecture, timeout, } } /// Execute agentic analysis with automatic architecture selection and timeout enforcement pub async fn execute( &self, query: String, analysis_type: AnalysisType, ) -> Result<CodeGraphAgentOutput, ExecutorError> { use std::time::Instant; // Create architecture-specific executor via factory let executor = self.factory.create(self.architecture)?; let project_root = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let enriched_query = match build_startup_context(&project_root) { Ok(ctx) => ctx.render_with_query(&query), Err(e) => { tracing::warn!(project = %project_root.display(), error = %e, "startup context unavailable"); query.clone() } }; let start_time = Instant::now(); // Wrap execution in timeout let result = tokio::time::timeout( self.timeout, executor.execute(enriched_query, analysis_type), ) .await; match result { Ok(inner_result) => { // Transform context overflow errors to user-friendly message inner_result.map_err(transform_context_overflow) } Err(_elapsed) => { let elapsed_secs = start_time.elapsed().as_secs(); // Log timeout event for debugging if std::env::var("CODEGRAPH_DEBUG").is_ok() { tracing::error!( target: "agent_execution_timeout", elapsed_secs = elapsed_secs, query_preview = %query.chars().take(100).collect::<String>(), analysis_type = ?analysis_type, architecture = %self.architecture, "Agent execution timed out" ); } // Still emit finish event even on timeout tracing::info!( target: "agent_execution_finish", status = "timeout", elapsed_secs = elapsed_secs, "Agent execution terminated due to timeout" ); Err(ExecutorError::Timeout { elapsed_secs, partial_result: None, // TODO: capture partial results from executor steps_completed: 0, // TODO: track steps in executor }) } } } } /// Builder for CodeGraphExecutor with fluent API pub struct CodeGraphExecutorBuilder { llm_provider: Option<Arc<dyn LLMProvider>>, tool_executor: Option<Arc<GraphToolExecutor>>, config: Option<Arc<codegraph_mcp_core::config_manager::CodeGraphConfig>>, } impl CodeGraphExecutorBuilder { pub fn new() -> Self { Self { llm_provider: None, tool_executor: None, config: None, } } pub fn llm_provider(mut self, provider: Arc<dyn LLMProvider>) -> Self { self.llm_provider = Some(provider); self } pub fn tool_executor(mut self, executor: Arc<GraphToolExecutor>) -> Self { self.tool_executor = Some(executor); self } pub fn config( mut self, config: Arc<codegraph_mcp_core::config_manager::CodeGraphConfig>, ) -> Self { self.config = Some(config); self } pub fn build(self) -> Result<CodeGraphExecutor, ExecutorError> { let llm_provider = self .llm_provider .ok_or_else(|| ExecutorError::BuildFailed("LLM provider required".to_string()))?; let tool_executor = self .tool_executor .ok_or_else(|| ExecutorError::BuildFailed("Tool executor required".to_string()))?; // Config is optional for backward compatibility // If not provided, create default config let config = self.config.unwrap_or_else(|| { Arc::new(codegraph_mcp_core::config_manager::CodeGraphConfig::default()) }); Ok(CodeGraphExecutor::new(llm_provider, tool_executor, config)) } } impl Default for CodeGraphExecutorBuilder { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_executor_builder_pattern() { let builder = CodeGraphExecutorBuilder::new(); // Builder should require both LLM provider and tool executor let result = builder.build(); assert!(result.is_err()); } #[test] fn test_executor_error_display() { let err = ExecutorError::BuildFailed("test".to_string()); assert_eq!(err.to_string(), "Agent build failed: test"); } #[test] fn test_timeout_error_display() { let err = ExecutorError::Timeout { elapsed_secs: 300, partial_result: Some("partial".to_string()), steps_completed: 5, }; assert_eq!( err.to_string(), "Agent execution timed out after 300 seconds" ); } #[test] fn test_context_overflow_error_display() { let err = ExecutorError::ContextOverflow("context_length_exceeded".to_string()); assert_eq!( err.to_string(), "Context window limit reached: context_length_exceeded" ); } #[test] fn test_read_timeout_config_default() { // Clear env var to test default std::env::remove_var("CODEGRAPH_AGENT_TIMEOUT_SECS"); let timeout = read_timeout_config(); assert_eq!(timeout.as_secs(), DEFAULT_TIMEOUT_SECS); } #[test] fn test_read_timeout_config_from_env() { std::env::set_var("CODEGRAPH_AGENT_TIMEOUT_SECS", "60"); let timeout = read_timeout_config(); assert_eq!(timeout.as_secs(), 60); std::env::remove_var("CODEGRAPH_AGENT_TIMEOUT_SECS"); } #[test] fn test_read_timeout_config_zero_means_unlimited() { std::env::set_var("CODEGRAPH_AGENT_TIMEOUT_SECS", "0"); let timeout = read_timeout_config(); // Zero means unlimited - should be very large assert!(timeout.as_secs() > 1_000_000); std::env::remove_var("CODEGRAPH_AGENT_TIMEOUT_SECS"); } #[test] fn test_read_timeout_config_invalid_fallback() { std::env::set_var("CODEGRAPH_AGENT_TIMEOUT_SECS", "not_a_number"); let timeout = read_timeout_config(); assert_eq!(timeout.as_secs(), DEFAULT_TIMEOUT_SECS); std::env::remove_var("CODEGRAPH_AGENT_TIMEOUT_SECS"); } #[test] fn test_is_context_overflow_error_positive() { assert!(is_context_overflow_error("context_length_exceeded")); assert!(is_context_overflow_error("maximum context length exceeded")); assert!(is_context_overflow_error("too many tokens in request")); assert!(is_context_overflow_error("token limit reached")); assert!(is_context_overflow_error("exceeds the maximum allowed")); } #[test] fn test_is_context_overflow_error_case_insensitive() { assert!(is_context_overflow_error("CONTEXT_LENGTH_EXCEEDED")); assert!(is_context_overflow_error("Maximum Context Length")); assert!(is_context_overflow_error("TOO MANY TOKENS")); } #[test] fn test_is_context_overflow_error_negative() { assert!(!is_context_overflow_error("connection failed")); assert!(!is_context_overflow_error("rate limit exceeded")); assert!(!is_context_overflow_error("authentication error")); assert!(!is_context_overflow_error("server error 500")); } #[test] fn test_transform_context_overflow_matches() { let err = ExecutorError::ExecutionFailed("Request failed: context_length_exceeded".to_string()); let transformed = transform_context_overflow(err); match transformed { ExecutorError::ContextOverflow(msg) => { assert!(msg.contains("Context window limit reached")); } _ => panic!("Expected ContextOverflow error"), } } #[test] fn test_transform_context_overflow_passthrough() { let err = ExecutorError::ExecutionFailed("connection timeout".to_string()); let transformed = transform_context_overflow(err); match transformed { ExecutorError::ExecutionFailed(msg) => { assert_eq!(msg, "connection timeout"); } _ => panic!("Expected ExecutionFailed error to pass through"), } } #[test] fn test_transform_other_error_types() { let err = ExecutorError::BuildFailed("test".to_string()); let transformed = transform_context_overflow(err); match transformed { ExecutorError::BuildFailed(msg) => { assert_eq!(msg, "test"); } _ => panic!("Expected BuildFailed error to pass through"), } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Jakedismo/codegraph-rust'

If you have feedback or need assistance with the MCP directory API, please join our Discord server