using MarketAlly.AIPlugin.Learning.Configuration;
using MarketAlly.AIPlugin.Learning.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Text.Json;
namespace MarketAlly.AIPlugin.Learning
{
///
/// Standalone plugin that provides unified context preparation combining
/// real-time code intelligence with historical memory and decision tracking
///
[AIPlugin("UnifiedContext", "Comprehensive context preparation combining real-time code analysis with historical insights and decision tracking")]
public class UnifiedContextPlugin : IAIPlugin, IDisposable
{
[AIParameter("Action to perform", required: true)]
public string Action { get; set; } = string.Empty;
[AIParameter("Query or topic for context preparation", required: false)]
public string Query { get; set; } = string.Empty;
[AIParameter("File path for targeted analysis", required: false)]
public string FilePath { get; set; } = string.Empty;
[AIParameter("Project path for context initialization", required: false)]
public string ProjectPath { get; set; } = string.Empty;
[AIParameter("Maximum tokens for context preparation", required: false)]
public int MaxTokens { get; set; } = 8000;
[AIParameter("Insight or decision content to store", required: false)]
public string Content { get; set; } = string.Empty;
[AIParameter("Category for insight storage", required: false)]
public string Category { get; set; } = string.Empty;
[AIParameter("Session summary for finalization", required: false)]
public string SessionSummary { get; set; } = string.Empty;
[AIParameter("Additional metadata as JSON string", required: false)]
public string Metadata { get; set; } = string.Empty;
public IReadOnlyDictionary SupportedParameters => new Dictionary
{
["action"] = typeof(string),
["query"] = typeof(string),
["filePath"] = typeof(string),
["projectPath"] = typeof(string),
["maxTokens"] = typeof(int),
["content"] = typeof(string),
["category"] = typeof(string),
["sessionSummary"] = typeof(string),
["metadata"] = typeof(string)
};
private IServiceProvider? _serviceProvider;
private bool _disposed = false;
public async Task ExecuteAsync(IReadOnlyDictionary parameters)
{
try
{
// Build service provider
_serviceProvider = BuildServiceProvider(parameters);
var unifiedContextService = _serviceProvider.GetRequiredService();
var action = parameters["action"].ToString()?.ToLower() ?? "";
return action switch
{
"prepare-context" => await PrepareContextAsync(unifiedContextService, parameters),
"initialize-session" => await InitializeSessionAsync(unifiedContextService, parameters),
"store-insight" => await StoreInsightAsync(unifiedContextService, parameters),
"find-similar" => await FindSimilarIssuesAsync(unifiedContextService, parameters),
"get-decisions" => await GetRelatedDecisionsAsync(unifiedContextService, parameters),
"store-decision" => await StoreDecisionAsync(unifiedContextService, parameters),
"finalize-session" => await FinalizeSessionAsync(unifiedContextService, parameters),
_ => new AIPluginResult(null, $"Unknown action: {action}. Supported actions: prepare-context, initialize-session, store-insight, find-similar, get-decisions, store-decision, finalize-session")
};
}
catch (Exception ex)
{
return new AIPluginResult(ex, $"UnifiedContext plugin failed: {ex.Message}");
}
}
private async Task PrepareContextAsync(IUnifiedContextService service, IReadOnlyDictionary parameters)
{
var query = parameters.GetValueOrDefault("query", "")?.ToString() ?? "";
var filePath = parameters.GetValueOrDefault("filePath", null)?.ToString();
var maxTokens = Convert.ToInt32(parameters.GetValueOrDefault("maxTokens", 8000));
if (string.IsNullOrWhiteSpace(query))
{
return new AIPluginResult(null, "Query parameter is required for context preparation");
}
var context = await service.PrepareFullContextAsync(query, filePath, maxTokens);
var result = new
{
Query = context.Query,
FilePath = context.FilePath,
GeneratedAt = context.GeneratedAt,
EstimatedTokens = context.EstimatedTotalTokens,
CurrentAnalysis = new
{
CodeChunks = context.CurrentCodeAnalysis?.CodeChunks?.Count ?? 0,
Tokens = context.CurrentCodeAnalysis?.EstimatedTokens ?? 0
},
HistoricalInsights = context.HistoricalInsights.Select(h => new
{
h.Summary,
h.Relevance,
h.Timestamp,
h.Tags
}).ToList(),
RelatedDecisions = context.RelatedDecisions.Select(d => new
{
d.Summary,
d.Successful,
d.Relevance,
d.Timestamp,
d.Tags
}).ToList(),
ProjectContext = context.ProjectContext != null ? new
{
context.ProjectContext.ProjectPath,
RecentChangesCount = context.ProjectContext.RecentChanges.Count,
context.ProjectContext.LastAnalyzed
} : null
};
return new AIPluginResult(result, $"Comprehensive context prepared: {context.EstimatedTotalTokens} tokens, {context.HistoricalInsights.Count} insights, {context.RelatedDecisions.Count} decisions");
}
private async Task InitializeSessionAsync(IUnifiedContextService service, IReadOnlyDictionary parameters)
{
var projectPath = parameters.GetValueOrDefault("projectPath", "")?.ToString() ?? "";
var query = parameters.GetValueOrDefault("query", "Learning session")?.ToString() ?? "Learning session";
if (string.IsNullOrWhiteSpace(projectPath))
{
return new AIPluginResult(null, "ProjectPath parameter is required for session initialization");
}
var sessionContext = await service.InitializeLearningSessionAsync(projectPath, query);
var result = new
{
sessionContext.SessionId,
sessionContext.ProjectPath,
sessionContext.Topic,
sessionContext.InitializedAt,
ProjectContext = sessionContext.ProjectContext != null ? new
{
RecentChangesCount = sessionContext.ProjectContext.RecentChanges.Count,
HasProjectPath = !string.IsNullOrEmpty(sessionContext.ProjectContext.ProjectPath),
sessionContext.ProjectContext.LastAnalyzed
} : null
};
return new AIPluginResult(result, $"Learning session initialized: {sessionContext.SessionId}");
}
private async Task StoreInsightAsync(IUnifiedContextService service, IReadOnlyDictionary parameters)
{
var content = parameters.GetValueOrDefault("content", "")?.ToString() ?? "";
var category = parameters.GetValueOrDefault("category", "general")?.ToString() ?? "general";
var filePath = parameters.GetValueOrDefault("filePath", null)?.ToString();
var metadataStr = parameters.GetValueOrDefault("metadata", "{}")?.ToString() ?? "{}";
if (string.IsNullOrWhiteSpace(content))
{
return new AIPluginResult(null, "Content parameter is required for insight storage");
}
Dictionary? metadata = null;
if (!string.IsNullOrWhiteSpace(metadataStr))
{
try
{
metadata = JsonSerializer.Deserialize>(metadataStr);
}
catch
{
return new AIPluginResult(null, "Invalid JSON format in metadata parameter");
}
}
await service.StoreLearningInsightAsync(content, category, filePath, metadata);
return new AIPluginResult(new { Success = true, Category = category, FilePath = filePath },
$"Insight stored successfully in category: {category}");
}
private async Task FindSimilarIssuesAsync(IUnifiedContextService service, IReadOnlyDictionary parameters)
{
var query = parameters.GetValueOrDefault("query", "")?.ToString() ?? "";
var projectPath = parameters.GetValueOrDefault("projectPath", null)?.ToString();
if (string.IsNullOrWhiteSpace(query))
{
return new AIPluginResult(null, "Query parameter is required for finding similar issues");
}
var insights = await service.FindSimilarPastIssuesAsync(query, projectPath);
var result = new
{
Query = query,
ResultCount = insights.Count,
Insights = insights.Select(i => new
{
i.Summary,
i.Relevance,
i.Timestamp,
i.Tags,
ContentPreview = i.Content.Count > 10 ? $"{i.Content.Count} items" : JsonSerializer.Serialize(i.Content)
}).ToList()
};
return new AIPluginResult(result, $"Found {insights.Count} similar past issues");
}
private async Task GetRelatedDecisionsAsync(IUnifiedContextService service, IReadOnlyDictionary parameters)
{
var query = parameters.GetValueOrDefault("query", "")?.ToString() ?? "";
if (string.IsNullOrWhiteSpace(query))
{
return new AIPluginResult(null, "Query parameter is required for finding related decisions");
}
var decisions = await service.GetRelatedDecisionsAsync(query);
var result = new
{
Query = query,
ResultCount = decisions.Count,
Decisions = decisions.Select(d => new
{
d.Summary,
d.Successful,
d.Relevance,
d.Timestamp,
d.Tags,
ContentPreview = d.Content.Count > 10 ? $"{d.Content.Count} items" : JsonSerializer.Serialize(d.Content)
}).ToList(),
SuccessfulCount = decisions.Count(d => d.Successful),
FailedCount = decisions.Count(d => !d.Successful)
};
return new AIPluginResult(result, $"Found {decisions.Count} related decisions ({decisions.Count(d => d.Successful)} successful, {decisions.Count(d => !d.Successful)} failed)");
}
private async Task StoreDecisionAsync(IUnifiedContextService service, IReadOnlyDictionary parameters)
{
var content = parameters.GetValueOrDefault("content", "")?.ToString() ?? "";
var filePath = parameters.GetValueOrDefault("filePath", "")?.ToString() ?? "";
var metadataStr = parameters.GetValueOrDefault("metadata", "{}")?.ToString() ?? "{}";
if (string.IsNullOrWhiteSpace(content) || string.IsNullOrWhiteSpace(filePath))
{
return new AIPluginResult(null, "Content and FilePath parameters are required for decision storage");
}
// Parse success from metadata or default to true
var successful = true;
try
{
if (!string.IsNullOrWhiteSpace(metadataStr))
{
var metadata = JsonSerializer.Deserialize>(metadataStr);
if (metadata?.ContainsKey("successful") == true)
{
successful = Convert.ToBoolean(metadata["successful"]);
}
}
}
catch
{
// Use default value
}
await service.StoreRefactoringDecisionAsync(content, "Decision stored via UnifiedContext plugin", filePath, successful);
return new AIPluginResult(new { Success = true, Outcome = successful ? "Successful" : "Failed", FilePath = filePath },
$"Decision stored for {Path.GetFileName(filePath)}: {(successful ? "successful" : "failed")}");
}
private async Task FinalizeSessionAsync(IUnifiedContextService service, IReadOnlyDictionary parameters)
{
var sessionSummary = parameters.GetValueOrDefault("sessionSummary", "")?.ToString() ?? "";
var metadataStr = parameters.GetValueOrDefault("metadata", "{}")?.ToString() ?? "{}";
if (string.IsNullOrWhiteSpace(sessionSummary))
{
return new AIPluginResult(null, "SessionSummary parameter is required for session finalization");
}
Dictionary metrics;
try
{
metrics = JsonSerializer.Deserialize>(metadataStr) ?? new Dictionary();
}
catch
{
return new AIPluginResult(null, "Invalid JSON format in metadata parameter");
}
var summary = await service.FinalizeLearningSessionAsync(sessionSummary, metrics);
var result = new
{
summary.SessionId,
summary.ProjectPath,
summary.Summary,
summary.FinalizedAt,
MetricsCount = summary.Metrics.Count
};
return new AIPluginResult(result, $"Session finalized: {summary.SessionId}");
}
private IServiceProvider BuildServiceProvider(IReadOnlyDictionary parameters)
{
var services = new ServiceCollection();
// Configuration
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary
{
["Learning:AI:EnableSemanticSearch"] = "true",
["Learning:AI:MaxSearchResults"] = "10",
["Learning:AI:MinSimilarityScore"] = "0.7",
["Learning:AI:MaxContextTokens"] = parameters.GetValueOrDefault("maxTokens", 8000).ToString(),
["Learning:Security:EnablePathValidation"] = "true",
["Learning:Security:EnableInputSanitization"] = "true"
})
.Build();
services.AddSingleton(configuration);
services.Configure(configuration.GetSection(LearningConfiguration.SectionName));
// Logging
services.AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information));
// Core services
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
// Mock RefactorIQ client (would need actual implementation)
// services.AddSingleton();
return services.BuildServiceProvider();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
if (_serviceProvider is IDisposable disposableProvider)
{
disposableProvider.Dispose();
}
}
_disposed = true;
}
}
}