MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.ClaudeCode/Services/ContextClaudeService.cs

366 lines
16 KiB
C#
Executable File

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace MarketAlly.AIPlugin.ClaudeCode;
/// <summary>
/// Implementation of context management service for Claude Code
/// </summary>
public class ContextClaudeService : IContextClaudeService
{
private readonly IRateLimitAwareHttpClient _httpClient;
private readonly ILogger<ContextClaudeService> _logger;
private readonly ClaudeCodeOptions _options;
public ContextClaudeService(
IRateLimitAwareHttpClient httpClient,
ILogger<ContextClaudeService> logger,
IOptions<ClaudeCodeOptions> options)
{
_httpClient = httpClient;
_logger = logger;
_options = options.Value;
}
public async Task<ClaudeCodeResponse<List<ContextEntry>>> GetRelevantContextAsync(
string query,
string? projectPath = null,
int maxResults = 10,
CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("Getting relevant context for query: {Query}", query);
var request = new
{
query = query,
projectPath = projectPath,
maxResults = maxResults
};
var response = await _httpClient.PostAsJsonAsync("/api/context/search", request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var error = $"Context search failed: {response.ErrorMessage}";
_logger.LogError(error);
return ClaudeCodeResponse<List<ContextEntry>>.CreateError(error, response.StatusCode.ToString(), response.RateLimitInfo);
}
var responseData = response.DeserializeJson<ContextSearchApiResponse>();
if (responseData?.Success != true)
{
var error = responseData?.Error ?? "Unknown error occurred";
_logger.LogError("Context API returned error: {Error}", error);
return ClaudeCodeResponse<List<ContextEntry>>.CreateError(error, "API_ERROR", response.RateLimitInfo);
}
_logger.LogDebug("Found {Count} relevant context entries", responseData.Results?.Count ?? 0);
return ClaudeCodeResponse<List<ContextEntry>>.CreateSuccess(responseData.Results ?? new List<ContextEntry>(), response.RateLimitInfo);
}
catch (RateLimitExceededException ex)
{
_logger.LogWarning("Rate limit exceeded: {Message}", ex.Message);
return ClaudeCodeResponse<List<ContextEntry>>.CreateError(ex.Message, "RATE_LIMITED", ex.RateLimitInfo);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting relevant context");
return ClaudeCodeResponse<List<ContextEntry>>.CreateError(ex.Message, "EXCEPTION");
}
}
public async Task<ClaudeCodeResponse<string>> StoreContextAsync(ContextEntry entry, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("Storing context entry: {Type}", entry.Type);
var response = await _httpClient.PostAsJsonAsync("/api/context/store", entry, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var error = $"Context storage failed: {response.ErrorMessage}";
_logger.LogError(error);
return ClaudeCodeResponse<string>.CreateError(error, response.StatusCode.ToString(), response.RateLimitInfo);
}
var responseData = response.DeserializeJson<ContextStoreApiResponse>();
if (responseData?.Success != true)
{
var error = responseData?.Error ?? "Unknown error occurred";
_logger.LogError("Context storage API returned error: {Error}", error);
return ClaudeCodeResponse<string>.CreateError(error, "API_ERROR", response.RateLimitInfo);
}
_logger.LogDebug("Context entry stored with ID: {Id}", responseData.Id);
return ClaudeCodeResponse<string>.CreateSuccess(responseData.Id ?? string.Empty, response.RateLimitInfo);
}
catch (RateLimitExceededException ex)
{
_logger.LogWarning("Rate limit exceeded: {Message}", ex.Message);
return ClaudeCodeResponse<string>.CreateError(ex.Message, "RATE_LIMITED", ex.RateLimitInfo);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error storing context");
return ClaudeCodeResponse<string>.CreateError(ex.Message, "EXCEPTION");
}
}
public async Task<ClaudeCodeResponse<List<ConversationMessage>>> GetConversationHistoryAsync(
string sessionId,
int maxMessages = 50,
CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("Getting conversation history for session: {SessionId}", sessionId);
var response = await _httpClient.GetAsync($"/api/conversations/{sessionId}/history?maxMessages={maxMessages}", cancellationToken);
if (!response.IsSuccessStatusCode)
{
var error = $"Conversation history request failed: {response.ErrorMessage}";
_logger.LogError(error);
return ClaudeCodeResponse<List<ConversationMessage>>.CreateError(error, response.StatusCode.ToString(), response.RateLimitInfo);
}
var responseData = response.DeserializeJson<ConversationHistoryApiResponse>();
if (responseData?.Success != true)
{
var error = responseData?.Error ?? "Unknown error occurred";
_logger.LogError("Conversation history API returned error: {Error}", error);
return ClaudeCodeResponse<List<ConversationMessage>>.CreateError(error, "API_ERROR", response.RateLimitInfo);
}
_logger.LogDebug("Retrieved {Count} conversation messages", responseData.Messages?.Count ?? 0);
return ClaudeCodeResponse<List<ConversationMessage>>.CreateSuccess(responseData.Messages ?? new List<ConversationMessage>(), response.RateLimitInfo);
}
catch (RateLimitExceededException ex)
{
_logger.LogWarning("Rate limit exceeded: {Message}", ex.Message);
return ClaudeCodeResponse<List<ConversationMessage>>.CreateError(ex.Message, "RATE_LIMITED", ex.RateLimitInfo);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting conversation history");
return ClaudeCodeResponse<List<ConversationMessage>>.CreateError(ex.Message, "EXCEPTION");
}
}
public async Task<ClaudeCodeResponse<bool>> StoreConversationMessageAsync(ConversationMessage message, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("Storing conversation message for session: {SessionId}", message.SessionId);
var response = await _httpClient.PostAsJsonAsync("/api/conversations/store-message", message, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var error = $"Message storage failed: {response.ErrorMessage}";
_logger.LogError(error);
return ClaudeCodeResponse<bool>.CreateError(error, response.StatusCode.ToString(), response.RateLimitInfo);
}
var responseData = response.DeserializeJson<SimpleApiResponse>();
if (responseData?.Success != true)
{
var error = responseData?.Error ?? "Unknown error occurred";
_logger.LogError("Message storage API returned error: {Error}", error);
return ClaudeCodeResponse<bool>.CreateError(error, "API_ERROR", response.RateLimitInfo);
}
_logger.LogDebug("Conversation message stored successfully");
return ClaudeCodeResponse<bool>.CreateSuccess(true, response.RateLimitInfo);
}
catch (RateLimitExceededException ex)
{
_logger.LogWarning("Rate limit exceeded: {Message}", ex.Message);
return ClaudeCodeResponse<bool>.CreateError(ex.Message, "RATE_LIMITED", ex.RateLimitInfo);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error storing conversation message");
return ClaudeCodeResponse<bool>.CreateError(ex.Message, "EXCEPTION");
}
}
public async Task<ClaudeCodeResponse<List<ProjectInsight>>> GetProjectInsightsAsync(
string projectPath,
string? category = null,
CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("Getting project insights for: {ProjectPath}", projectPath);
var queryParams = $"projectPath={Uri.EscapeDataString(projectPath)}";
if (!string.IsNullOrEmpty(category))
{
queryParams += $"&category={Uri.EscapeDataString(category)}";
}
var response = await _httpClient.GetAsync($"/api/learning/insights?{queryParams}", cancellationToken);
if (!response.IsSuccessStatusCode)
{
var error = $"Project insights request failed: {response.ErrorMessage}";
_logger.LogError(error);
return ClaudeCodeResponse<List<ProjectInsight>>.CreateError(error, response.StatusCode.ToString(), response.RateLimitInfo);
}
var responseData = response.DeserializeJson<ProjectInsightsApiResponse>();
if (responseData?.Success != true)
{
var error = responseData?.Error ?? "Unknown error occurred";
_logger.LogError("Project insights API returned error: {Error}", error);
return ClaudeCodeResponse<List<ProjectInsight>>.CreateError(error, "API_ERROR", response.RateLimitInfo);
}
_logger.LogDebug("Retrieved {Count} project insights", responseData.Insights?.Count ?? 0);
return ClaudeCodeResponse<List<ProjectInsight>>.CreateSuccess(responseData.Insights ?? new List<ProjectInsight>(), response.RateLimitInfo);
}
catch (RateLimitExceededException ex)
{
_logger.LogWarning("Rate limit exceeded: {Message}", ex.Message);
return ClaudeCodeResponse<List<ProjectInsight>>.CreateError(ex.Message, "RATE_LIMITED", ex.RateLimitInfo);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting project insights");
return ClaudeCodeResponse<List<ProjectInsight>>.CreateError(ex.Message, "EXCEPTION");
}
}
public async Task<ClaudeCodeResponse<string>> StoreProjectInsightAsync(ProjectInsight insight, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("Storing project insight: {Type} - {Title}", insight.Type, insight.Title);
var response = await _httpClient.PostAsJsonAsync("/api/learning/store-insight", insight, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var error = $"Project insight storage failed: {response.ErrorMessage}";
_logger.LogError(error);
return ClaudeCodeResponse<string>.CreateError(error, response.StatusCode.ToString(), response.RateLimitInfo);
}
var responseData = response.DeserializeJson<ContextStoreApiResponse>();
if (responseData?.Success != true)
{
var error = responseData?.Error ?? "Unknown error occurred";
_logger.LogError("Project insight storage API returned error: {Error}", error);
return ClaudeCodeResponse<string>.CreateError(error, "API_ERROR", response.RateLimitInfo);
}
_logger.LogDebug("Project insight stored with ID: {Id}", responseData.Id);
return ClaudeCodeResponse<string>.CreateSuccess(responseData.Id ?? string.Empty, response.RateLimitInfo);
}
catch (RateLimitExceededException ex)
{
_logger.LogWarning("Rate limit exceeded: {Message}", ex.Message);
return ClaudeCodeResponse<string>.CreateError(ex.Message, "RATE_LIMITED", ex.RateLimitInfo);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error storing project insight");
return ClaudeCodeResponse<string>.CreateError(ex.Message, "EXCEPTION");
}
}
public async Task<ClaudeCodeResponse<List<ContextSearchResult>>> SearchAllContextAsync(
string query,
ContextSearchFilters? filters = null,
CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("Searching all context with query: {Query}", query);
var request = new
{
query = query,
filters = filters
};
var response = await _httpClient.PostAsJsonAsync("/api/context/search-all", request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var error = $"Context search failed: {response.ErrorMessage}";
_logger.LogError(error);
return ClaudeCodeResponse<List<ContextSearchResult>>.CreateError(error, response.StatusCode.ToString(), response.RateLimitInfo);
}
var responseData = response.DeserializeJson<AllContextSearchApiResponse>();
if (responseData?.Success != true)
{
var error = responseData?.Error ?? "Unknown error occurred";
_logger.LogError("Context search API returned error: {Error}", error);
return ClaudeCodeResponse<List<ContextSearchResult>>.CreateError(error, "API_ERROR", response.RateLimitInfo);
}
_logger.LogDebug("Found {Count} context search results", responseData.Results?.Count ?? 0);
return ClaudeCodeResponse<List<ContextSearchResult>>.CreateSuccess(responseData.Results ?? new List<ContextSearchResult>(), response.RateLimitInfo);
}
catch (RateLimitExceededException ex)
{
_logger.LogWarning("Rate limit exceeded: {Message}", ex.Message);
return ClaudeCodeResponse<List<ContextSearchResult>>.CreateError(ex.Message, "RATE_LIMITED", ex.RateLimitInfo);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error searching context");
return ClaudeCodeResponse<List<ContextSearchResult>>.CreateError(ex.Message, "EXCEPTION");
}
}
}
/// <summary>
/// API response models for context operations
/// </summary>
internal class ContextSearchApiResponse
{
public bool Success { get; set; }
public List<ContextEntry>? Results { get; set; }
public string? Error { get; set; }
}
internal class ContextStoreApiResponse
{
public bool Success { get; set; }
public string? Id { get; set; }
public string? Error { get; set; }
}
internal class ConversationHistoryApiResponse
{
public bool Success { get; set; }
public List<ConversationMessage>? Messages { get; set; }
public string? Error { get; set; }
}
internal class ProjectInsightsApiResponse
{
public bool Success { get; set; }
public List<ProjectInsight>? Insights { get; set; }
public string? Error { get; set; }
}
internal class AllContextSearchApiResponse
{
public bool Success { get; set; }
public List<ContextSearchResult>? Results { get; set; }
public string? Error { get; set; }
}
internal class SimpleApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
}