using MarketAlly.AIPlugin; using MarketAlly.AIPlugin.Context; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; namespace Test.Context; /// /// Simplified ContextClaudeService that uses AIPluginHelper properly /// public class ContextClaudeService { private readonly ILogger _logger; private readonly AIPluginRegistry _registry; private readonly Claude4Settings _settings; private readonly HttpClient _httpClient; private string _currentSessionId = ""; private string _currentTopic = ""; private string _currentProject = ""; public ContextClaudeService( ILogger logger, AIPluginRegistry registry, IOptions settings, HttpClient httpClient) { _logger = logger; _registry = registry; _settings = settings.Value; _httpClient = httpClient; } #region Web API Methods /// /// Process a single message for web API - returns Claude's response /// public async Task ProcessSingleMessageAsync(string message, string topic, string projectPath, List? previousMessages = null) { try { _logger.LogInformation("Processing single message for topic: {Topic}", topic); _currentTopic = topic; _currentProject = projectPath; _currentSessionId = Guid.NewGuid().ToString(); // Build conversation history var messages = new List(); // Add system message with context var systemPrompt = await BuildSystemPromptWithContextAsync(topic, projectPath); messages.Add(new { role = "system", content = systemPrompt }); // Add previous messages if provided if (previousMessages != null && previousMessages.Any()) { messages.AddRange(previousMessages.TakeLast(10)); } // Add current message messages.Add(new { role = "user", content = message }); // Call Claude using AIPluginHelper var claudeResponse = await CallClaudeWithAIPluginHelperAsync(messages); // Auto-store important information await AutoStoreImportantInformationAsync(message, claudeResponse); return claudeResponse; } catch (Exception ex) { _logger.LogError(ex, "Unexpected error processing message"); throw new InvalidOperationException("Claude integration temporarily unavailable. Please try again later.", ex); } } /// /// Process a command for web API /// public async Task ProcessCommandAsync(string command, List arguments, string projectPath) { try { _currentProject = projectPath; var cmd = command.ToLower(); return cmd switch { "/search" => await HandleSearchCommandAsync(arguments, projectPath), "/context" => await HandleContextCommandAsync(projectPath), "/store" => await HandleStoreCommandAsync(arguments, projectPath), "/project" => await HandleProjectCommandAsync(projectPath), "/help" => HandleHelpCommand(), _ => $"Unknown command: {command}. Available commands: /search, /context, /store, /project, /help" }; } catch (Exception ex) { _logger.LogError(ex, "Failed to process command: {Command}", command); return $"Failed to execute command '{command}'. Please try again."; } } /// /// Initialize a session and return initialization data /// public async Task InitializeSessionAsync(string topic, string projectPath) { try { _currentTopic = topic; _currentProject = projectPath; _currentSessionId = Guid.NewGuid().ToString(); var result = await _registry.CallFunctionAsync("ConversationContinuity", new Dictionary { ["action"] = "initialize", ["topic"] = topic, ["projectPath"] = projectPath }); return new { SessionId = _currentSessionId, Topic = topic, ProjectPath = projectPath, StartTime = DateTime.UtcNow, Success = result.Success, RecentContext = result.Success ? result.Data : null, Message = result.Success ? "Session initialized with context" : "Session initialized without context" }; } catch (Exception ex) { _logger.LogError(ex, "Failed to initialize session"); return new { SessionId = Guid.NewGuid().ToString(), Topic = topic, ProjectPath = projectPath, StartTime = DateTime.UtcNow, Success = false, Error = "Session initialization failed" }; } } #endregion #region Interactive Chat Methods /// /// Interactive Claude chat mode with context management /// public async Task InteractiveContextChatAsync(string topic, string projectPath) { try { Console.WriteLine("=".PadRight(60, '=')); Console.WriteLine("CLAUDE INTERACTIVE CHAT WITH CONTEXT MANAGEMENT"); Console.WriteLine("=".PadRight(60, '=')); Console.WriteLine($"Topic: {topic}"); Console.WriteLine($"Project: {projectPath}"); Console.WriteLine(); // Initialize session await InitializeSessionAsync(topic, projectPath); var messages = new List(); var systemPrompt = await BuildSystemPromptWithContextAsync(topic, projectPath); messages.Add(new { role = "system", content = systemPrompt }); Console.WriteLine("Chat started! Type '/end' to finish."); Console.WriteLine(); while (true) { Console.Write("You: "); var userInput = Console.ReadLine()?.Trim(); if (string.IsNullOrEmpty(userInput)) continue; if (userInput == "/end") break; // Handle commands if (userInput.StartsWith("/")) { var parts = userInput.Split(' ', 2); var cmd = parts[0]; var args = parts.Length > 1 ? parts[1].Split(' ').ToList() : new List(); var cmdResult = await ProcessCommandAsync(cmd, args, projectPath); Console.WriteLine($"[COMMAND] {cmdResult}"); Console.WriteLine(); continue; } // Add user message messages.Add(new { role = "user", content = userInput }); try { var claudeResponse = await CallClaudeWithAIPluginHelperAsync(messages); Console.WriteLine($"Claude: {claudeResponse}"); Console.WriteLine(); // Add Claude's response messages.Add(new { role = "assistant", content = claudeResponse }); // Auto-store important information await AutoStoreImportantInformationAsync(userInput, claudeResponse); } catch (Exception ex) { Console.WriteLine($"[ERROR] {ex.Message}"); Console.WriteLine(); } } return "Interactive chat session completed"; } catch (Exception ex) { Console.WriteLine($"[ERROR] Interactive chat failed: {ex.Message}"); return $"Chat session failed: {ex.Message}"; } } #endregion #region Core Claude Integration Using AIPluginHelper /// /// Call Claude using the existing AIPluginHelper framework /// private async Task CallClaudeWithAIPluginHelperAsync(List messages) { try { // Get function definitions from registry var functionDefinitions = _registry.GetAllPluginSchemas(); // Convert messages to format expected by AIPluginHelper var dynamicMessages = messages.Select(msg => { var msgJson = JsonSerializer.Serialize(msg); var msgDict = JsonSerializer.Deserialize>(msgJson); return new { Role = msgDict?["role"]?.ToString() ?? "user", Content = msgDict?["content"]?.ToString() ?? "" }; }); // Use AIPluginHelper to build request var requestJson = AIPluginHelper.SerializeRequestWithTools( AIPluginHelper.AIModel.Claude, dynamicMessages, functionDefinitions, _settings.DefaultModel, _settings.DefaultTemperature, _settings.DefaultMaxTokens, "auto" ); // Send request var responseJson = await SendClaudeRequestAsync(requestJson); // Process response var claudeResponse = JsonSerializer.Deserialize(responseJson); return await ProcessClaudeResponseAsync(claudeResponse, messages); } catch (Exception ex) { _logger.LogError(ex, "Claude API call failed"); throw; } } /// /// Send request to Claude API /// private async Task SendClaudeRequestAsync(string requestJson) { _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Add("x-api-key", _settings.ApiKey); _httpClient.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01"); var content = new StringContent(requestJson, Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync("https://api.anthropic.com/v1/messages", content); if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(); throw new HttpRequestException($"Claude API error: {response.StatusCode} - {errorContent}"); } return await response.Content.ReadAsStringAsync(); } /// /// Process Claude's response and handle tool calls /// private async Task ProcessClaudeResponseAsync(ClaudeApiResponse claudeResponse, List messages) { if (claudeResponse?.Content == null || !claudeResponse.Content.Any()) { return "I apologize, but I didn't receive a proper response. Please try again."; } var textResponse = ""; var hasToolCalls = false; // Process each content block foreach (var block in claudeResponse.Content) { if (block.Type == "text") { textResponse += block.Text; } else if (block.Type == "tool_use") { hasToolCalls = true; // Execute the tool call var toolResult = await ExecutePluginAsync(block.Name, block.Input ?? new Dictionary()); // For now, just append tool results to text response textResponse += $"\n\n[Tool Result: {toolResult}]"; } } return textResponse.Trim(); } /// /// Execute a plugin using the registry /// private async Task ExecutePluginAsync(string? pluginName, Dictionary parameters) { try { if (string.IsNullOrEmpty(pluginName)) return "Invalid plugin name"; // Convert JsonElement parameters to proper types var convertedParameters = ConvertJsonElementParameters(parameters); var result = await _registry.CallFunctionAsync(pluginName, convertedParameters); if (result.Success) { return JsonSerializer.Serialize(result.Data, new JsonSerializerOptions { WriteIndented = true }); } else { return $"Plugin execution failed: {result.Message}"; } } catch (Exception ex) { _logger.LogError(ex, "Plugin execution failed: {PluginName}", pluginName); return $"Error executing {pluginName}: {ex.Message}"; } } /// /// Convert JsonElement parameters to proper .NET types /// private Dictionary ConvertJsonElementParameters(Dictionary parameters) { var converted = new Dictionary(); foreach (var param in parameters) { if (param.Value is JsonElement jsonElement) { converted[param.Key] = ConvertJsonElementToObject(jsonElement); } else { converted[param.Key] = param.Value; } } return converted; } private object ConvertJsonElementToObject(JsonElement element) { switch (element.ValueKind) { case JsonValueKind.String: return element.GetString() ?? ""; case JsonValueKind.Number: if (element.TryGetInt32(out int intValue)) return intValue; return element.GetDouble(); case JsonValueKind.True: return true; case JsonValueKind.False: return false; case JsonValueKind.Null: return null; case JsonValueKind.Array: var array = new List(); foreach (var item in element.EnumerateArray()) { array.Add(ConvertJsonElementToObject(item)); } return array; case JsonValueKind.Object: var dict = new Dictionary(); foreach (var property in element.EnumerateObject()) { dict[property.Name] = ConvertJsonElementToObject(property.Value); } return dict; default: return element.GetRawText(); } } #endregion #region Helper Methods private async Task BuildSystemPromptWithContextAsync(string topic, string projectPath) { var prompt = new StringBuilder(); prompt.AppendLine($"You are Claude, an AI assistant helping with: {topic}"); prompt.AppendLine(); prompt.AppendLine("You have access to context management tools for storing and retrieving information."); prompt.AppendLine("Use these tools to maintain conversation continuity and reference previous discussions."); return prompt.ToString(); } private async Task AutoStoreImportantInformationAsync(string userInput, string claudeResponse) { var importantKeywords = new[] { "decision", "choose", "implement", "recommend", "solution", "approach", "decided" }; if (importantKeywords.Any(keyword => claudeResponse.ToLower().Contains(keyword))) { try { await _registry.CallFunctionAsync("ContextStorage", new Dictionary { ["contextType"] = "insight", ["summary"] = $"Important discussion point ({DateTime.Now:HH:mm})", ["content"] = $"User: {userInput}\n\nClaude: {claudeResponse}", ["priority"] = "medium", ["tags"] = $"{_currentTopic},auto-stored,important", ["projectPath"] = _currentProject }); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to auto-store important information"); } } } #endregion #region Command Handlers private async Task HandleSearchCommandAsync(List arguments, string projectPath) { if (!arguments.Any()) return "Usage: /search "; var query = string.Join(" ", arguments); var result = await _registry.CallFunctionAsync("ContextSearch", new Dictionary { ["query"] = query, ["maxResults"] = 5, ["includeContent"] = true, ["projectPath"] = projectPath }); return result.Success ? FormatSearchResults(query, result.Data) : $"Search failed: {result.Message}"; } private async Task HandleContextCommandAsync(string projectPath) { var result = await _registry.CallFunctionAsync("ContextRetrieval", new Dictionary { ["contextType"] = "all", ["projectPath"] = projectPath, ["conversationLimit"] = 5 }); return result.Success ? "Context retrieved successfully" : $"Failed: {result.Message}"; } private async Task HandleStoreCommandAsync(List arguments, string projectPath) { if (!arguments.Any()) return "Usage: /store "; var summary = string.Join(" ", arguments); var result = await _registry.CallFunctionAsync("ContextStorage", new Dictionary { ["contextType"] = "conversation", ["summary"] = summary, ["content"] = $"Manual storage: {summary}", ["priority"] = "medium", ["tags"] = $"{_currentTopic},manual-store", ["projectPath"] = projectPath }); return result.Success ? $"✅ Stored: {summary}" : $"❌ Storage failed: {result.Message}"; } private async Task HandleProjectCommandAsync(string projectPath) { var result = await _registry.CallFunctionAsync("ConversationContinuity", new Dictionary { ["action"] = "get_project_context", ["projectPath"] = projectPath }); return result.Success ? "Project context retrieved" : $"Failed: {result.Message}"; } private string HandleHelpCommand() { return @"Available Commands: /context - Show recent context /search - Search stored context /store - Store discussion point /project - Show project context /help - Show this help"; } private string FormatSearchResults(string query, object? data) { return $"Search results for '{query}' - {(data != null ? "Found results" : "No results")}"; } #endregion #region Claude API Response Models public class ClaudeApiResponse { [JsonPropertyName("content")] public List Content { get; set; } = new(); } public class ClaudeContentBlock { [JsonPropertyName("type")] public string Type { get; set; } = ""; [JsonPropertyName("text")] public string? Text { get; set; } [JsonPropertyName("name")] public string? Name { get; set; } [JsonPropertyName("input")] public Dictionary? Input { get; set; } } #endregion #region Public Convenience Methods public async Task StoreImportantDecisionAsync(string sessionId, string userMessage, string assistantResponse, string? projectPath = null) { try { var result = await _registry.CallFunctionAsync("ContextStorage", new Dictionary { ["contextType"] = "conversation", ["summary"] = $"Chat decision: {userMessage.Substring(0, Math.Min(50, userMessage.Length))}...", ["content"] = $"**User:** {userMessage}\n\n**Claude:** {assistantResponse}", ["priority"] = "medium", ["tags"] = "chat-session,auto-stored,important-decision", ["projectPath"] = projectPath ?? _currentProject }); return result.Success; } catch (Exception ex) { _logger.LogError(ex, "Failed to store important decision"); return false; } } #endregion } // Add Claude4Settings for your Test.Context project public class Claude4Settings { public string ApiKey { get; set; } = ""; public string DefaultModel { get; set; } = "claude-3-5-sonnet-20241022"; public double DefaultTemperature { get; set; } = 0.3; public int DefaultMaxTokens { get; set; } = 8000; }