Files
nexus/backend/Services/OpenClawGatewayClient.cs
AzuTear f5552218bc
CI - Build & Test / Backend (.NET) (push) Successful in 42s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 2m46s
CI - Build & Test / Security Check (push) Successful in 3s
CI - Build & Test / Deploy Nexus (push) Successful in 56s
feat: ship agent-first mission control v0.2.57
2026-07-31 22:39:47 +02:00

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;
}
}