using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Text.Json; namespace MarketAlly.AIPlugin.ClaudeCode; /// /// Main implementation of Claude Code integration service /// public class ClaudeCodeService : IClaudeCodeService { private readonly IRateLimitAwareHttpClient _httpClient; private readonly IContextClaudeService _contextService; private readonly ILogger _logger; private readonly ClaudeCodeOptions _options; public ClaudeCodeService( IRateLimitAwareHttpClient httpClient, IContextClaudeService contextService, ILogger logger, IOptions options) { _httpClient = httpClient; _contextService = contextService; _logger = logger; _options = options.Value; // Configure HTTP client _httpClient.Configure(_options.BaseUrl, _options.ApiKey ?? string.Empty, _options.TenantId); } public async Task> SendChatMessageAsync(ChatRequest request, CancellationToken cancellationToken = default) { try { _logger.LogDebug("Sending chat message: {Message}", request.Message); // Get relevant context if requested List? relevantContext = null; if (request.IncludeProjectContext && !string.IsNullOrEmpty(request.ProjectPath)) { var contextResponse = await _contextService.GetRelevantContextAsync( request.Message, request.ProjectPath, 5, cancellationToken); if (contextResponse.Success) { relevantContext = contextResponse.Data; } } // Get conversation history if requested List? history = null; if (request.IncludeHistory && !string.IsNullOrEmpty(request.SessionId)) { var historyResponse = await _contextService.GetConversationHistoryAsync( request.SessionId, 10, cancellationToken); if (historyResponse.Success) { history = historyResponse.Data; } } // Prepare the API request var apiRequest = new { message = request.Message, sessionId = request.SessionId, projectPath = request.ProjectPath, context = request.Context, relevantContext = relevantContext?.Select(c => new { c.Type, c.Content, c.Metadata }), conversationHistory = history?.Select(h => new { h.Role, h.Content, h.Timestamp }) }; // Send request to Aizia API var response = await _httpClient.PostAsJsonAsync("/api/chat/send", apiRequest, cancellationToken); if (!response.IsSuccessStatusCode) { var error = $"Chat request failed: {response.ErrorMessage}"; _logger.LogError(error); return ClaudeCodeResponse.CreateError(error, response.StatusCode.ToString(), response.RateLimitInfo); } var responseData = response.DeserializeJson(); if (responseData?.Success != true) { var error = responseData?.Error ?? "Unknown error occurred"; _logger.LogError("Chat API returned error: {Error}", error); return ClaudeCodeResponse.CreateError(error, "API_ERROR", response.RateLimitInfo); } // Store the conversation if (!string.IsNullOrEmpty(request.SessionId) && !string.IsNullOrEmpty(responseData.Response)) { await StoreConversationAsync(request.SessionId, request.Message, responseData.Response, request.ProjectPath); } _logger.LogDebug("Chat message sent successfully"); return ClaudeCodeResponse.CreateSuccess(responseData.Response ?? string.Empty, response.RateLimitInfo); } catch (RateLimitExceededException ex) { _logger.LogWarning("Rate limit exceeded: {Message}", ex.Message); return ClaudeCodeResponse.CreateError(ex.Message, "RATE_LIMITED", ex.RateLimitInfo); } catch (Exception ex) { _logger.LogError(ex, "Error sending chat message"); return ClaudeCodeResponse.CreateError(ex.Message, "EXCEPTION"); } } public async Task> AnalyzeCodeAsync(AnalysisRequest request, CancellationToken cancellationToken = default) { try { _logger.LogDebug("Analyzing code: {FilePath}", request.FilePath ?? "inline code"); var apiRequest = new { filePath = request.FilePath, code = request.Code, analysisType = request.AnalysisType, projectPath = request.ProjectPath, options = request.Options }; var response = await _httpClient.PostAsJsonAsync("/api/chat/analyze", apiRequest, cancellationToken); if (!response.IsSuccessStatusCode) { var error = $"Analysis request failed: {response.ErrorMessage}"; _logger.LogError(error); return ClaudeCodeResponse.CreateError(error, response.StatusCode.ToString(), response.RateLimitInfo); } var responseData = response.DeserializeJson(); if (responseData?.Success != true) { var error = responseData?.Error ?? "Unknown error occurred"; _logger.LogError("Analysis API returned error: {Error}", error); return ClaudeCodeResponse.CreateError(error, "API_ERROR", response.RateLimitInfo); } _logger.LogDebug("Code analysis completed successfully"); return ClaudeCodeResponse.CreateSuccess(responseData.Result!, response.RateLimitInfo); } catch (RateLimitExceededException ex) { _logger.LogWarning("Rate limit exceeded: {Message}", ex.Message); return ClaudeCodeResponse.CreateError(ex.Message, "RATE_LIMITED", ex.RateLimitInfo); } catch (Exception ex) { _logger.LogError(ex, "Error analyzing code"); return ClaudeCodeResponse.CreateError(ex.Message, "EXCEPTION"); } } public async Task> GetRateLimitStatusAsync(CancellationToken cancellationToken = default) { try { var rateLimitInfo = await _httpClient.GetRateLimitStatusAsync(cancellationToken); return ClaudeCodeResponse.CreateSuccess(rateLimitInfo); } catch (Exception ex) { _logger.LogError(ex, "Error getting rate limit status"); return ClaudeCodeResponse.CreateError(ex.Message, "EXCEPTION"); } } public async Task>> SearchContextAsync(string query, string? projectPath = null, CancellationToken cancellationToken = default) { try { _logger.LogDebug("Searching context: {Query}", query); var filters = new ContextSearchFilters { ProjectPath = projectPath, MaxResults = 20 }; var response = await _contextService.SearchAllContextAsync(query, filters, cancellationToken); return response; } catch (Exception ex) { _logger.LogError(ex, "Error searching context"); return ClaudeCodeResponse>.CreateError(ex.Message, "EXCEPTION"); } } public async Task> StoreDecisionAsync(StoreDecisionRequest request, CancellationToken cancellationToken = default) { try { _logger.LogDebug("Storing decision: {Decision}", request.Decision); var insight = new ProjectInsight { ProjectPath = request.ProjectPath ?? string.Empty, Type = "decision", Category = request.Category, Title = "User Decision", Description = request.Decision, Data = request.Metadata ?? new Dictionary() }; var response = await _contextService.StoreProjectInsightAsync(insight, cancellationToken); return ClaudeCodeResponse.CreateSuccess(response.Success); } catch (Exception ex) { _logger.LogError(ex, "Error storing decision"); return ClaudeCodeResponse.CreateError(ex.Message, "EXCEPTION"); } } private async Task StoreConversationAsync(string sessionId, string userMessage, string assistantResponse, string? projectPath) { try { // Store user message var userMsg = new ConversationMessage { SessionId = sessionId, Role = "user", Content = userMessage, ProjectPath = projectPath }; await _contextService.StoreConversationMessageAsync(userMsg); // Store assistant response var assistantMsg = new ConversationMessage { SessionId = sessionId, Role = "assistant", Content = assistantResponse, ProjectPath = projectPath }; await _contextService.StoreConversationMessageAsync(assistantMsg); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to store conversation messages"); } } } /// /// API response models /// internal class ChatApiResponse { public bool Success { get; set; } public string? Response { get; set; } public string? Error { get; set; } } internal class AnalysisApiResponse { public bool Success { get; set; } public AnalysisResult? Result { get; set; } public string? Error { get; set; } }