163 lines
5.4 KiB
C#
163 lines
5.4 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Temporary, bounded HTTP compatibility adapter for session history.
|
|
/// All OpenClaw inventory and management operations use the Protocol-v4
|
|
/// connector and <see cref="IOpenClawControlService"/>.
|
|
/// </summary>
|
|
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<List<MessageEntry>> GetSessionHistoryAsync(
|
|
string sessionKey,
|
|
int limit = 50,
|
|
int offset = 0)
|
|
{
|
|
var result = new List<MessageEntry>();
|
|
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<JsonNode?> 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<string, object?>
|
|
{
|
|
["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<bool>() == 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;
|
|
}
|
|
}
|