using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes; using Nexus.Api.Models; namespace Nexus.Api.Services; /// /// Temporary, bounded HTTP compatibility adapter for session history. /// All OpenClaw inventory and management operations use the Protocol-v4 /// connector and . /// public sealed class OpenClawGatewayClient( HttpClient httpClient, IConfiguration configuration) : IOpenClawGatewayClient { private static readonly string[] SensitiveMarkers = [ "api_key", "apikey", "api-key", "authorization", "bearer ", "password", "token", "secret", "x-nexus-api-key", "jwt", "private_key" ]; public async Task> GetSessionHistoryAsync( string sessionKey, int limit = 50, int offset = 0) { var result = new List(); try { var toolResult = await InvokeToolAsync("sessions_history", new { sessionKey, limit, offset, includeTools = false }); if (toolResult is null) return result; using var document = JsonDocument.Parse(toolResult.ToJsonString()); var root = document.RootElement; if (!TryGetMessages(root, out var messages)) return result; foreach (var message in messages.EnumerateArray()) { if (!message.TryGetProperty("role", out var roleElement)) continue; var role = roleElement.GetString(); if (role is not ("user" or "assistant")) continue; if (!message.TryGetProperty("content", out var contentElement) || contentElement.ValueKind != JsonValueKind.Array) { continue; } var text = contentElement .EnumerateArray() .Where(block => block.TryGetProperty("type", out var type) && type.GetString() == "text" && block.TryGetProperty("text", out _)) .Select(block => block.GetProperty("text").GetString()) .Where(value => !string.IsNullOrWhiteSpace(value)); var content = string.Join(" ", text).Trim(); if (string.IsNullOrWhiteSpace(content) || content is "REPLY_SKIP" or "ANNOUNCE_SKIP") { continue; } var timestamp = message.TryGetProperty("timestamp", out var timestampElement) ? timestampElement.GetString() : null; result.Add(new MessageEntry( role, content, timestamp ?? DateTimeOffset.UtcNow.ToString("O"))); } } catch { // Session history is an optional dashboard projection. The caller // handles an empty result without inventing messages. } return result; } public static string RedactSensitiveText(string content) { if (string.IsNullOrWhiteSpace(content)) return content; var lines = content.Split('\n'); for (var index = 0; index < lines.Length; index++) { var lower = lines[index].ToLowerInvariant(); if (SensitiveMarkers.Any(marker => lower.Contains(marker, StringComparison.OrdinalIgnoreCase))) { lines[index] = "[redacted sensitive line]"; } } return string.Join('\n', lines); } private async Task InvokeToolAsync(string tool, object args) { using var request = new HttpRequestMessage(HttpMethod.Post, "/tools/invoke"); var credential = configuration["Integrations:OpenClaw:Password"]; if (string.IsNullOrWhiteSpace(credential)) credential = configuration["Integrations:OpenClaw:Token"]; if (!string.IsNullOrWhiteSpace(credential)) request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", credential); request.Content = JsonContent.Create(new Dictionary { ["tool"] = tool, ["args"] = args }); using var response = await httpClient.SendAsync(request); if (!response.IsSuccessStatusCode) return null; var json = await response.Content.ReadAsStringAsync(); if (string.IsNullOrWhiteSpace(json)) return null; var node = JsonNode.Parse(json); return node?["ok"]?.GetValue() == true && node["result"] is not null ? node["result"] : node; } private static bool TryGetMessages( JsonElement root, out JsonElement messages) { if (root.TryGetProperty("details", out var details) && details.TryGetProperty("messages", out messages) && messages.ValueKind == JsonValueKind.Array) { return true; } if (root.TryGetProperty("messages", out messages) && messages.ValueKind == JsonValueKind.Array) { return true; } messages = default; return false; } }