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; }
}