using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; using System.Diagnostics; using System.Security.Claims; using Nexus.Api.DTOs; using Nexus.Api.Services; namespace Nexus.Api.Controllers; [Authorize(Roles = "owner")] [ApiController] [Route("api/v1/chat")] public class ChatController( IOpenClawChatService chat, ILogger logger) : ControllerBase { [HttpPost] [EnableRateLimiting("agents")] public async Task Chat([FromBody] ChatRequest request, CancellationToken ct) { var message = request.Message?.Trim(); if (string.IsNullOrWhiteSpace(message) || message.Length > 8000) return Results.ValidationProblem(new Dictionary { ["message"] = ["Message must contain between 1 and 8000 characters."] }); var agentId = string.IsNullOrWhiteSpace(request.AgentId) ? "iris" : request.AgentId.Trim().ToLowerInvariant(); if (agentId is not ("iris" or "main")) return Results.ValidationProblem(new Dictionary { ["agentId"] = ["Only iris and main are supported."] }); var conversationId = string.IsNullOrWhiteSpace(request.ConversationId) ? $"nexus-{Guid.NewGuid():N}" : request.ConversationId.Trim(); if (conversationId.Length > 160) return Results.ValidationProblem(new Dictionary { ["conversationId"] = ["Conversation id is too long."] }); try { var contextualMessage = MissionControlContextFormatter.Format(message, request.Context); var invocationContext = OpenClawInvocationContextFactory.Create( User.FindFirst("sub")?.Value ?? User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? User.Identity?.Name, Request.Headers["Idempotency-Key"].FirstOrDefault(), Request.Headers["X-Correlation-ID"].FirstOrDefault() ?? HttpContext.TraceIdentifier, Request.Headers["traceparent"].FirstOrDefault() ?? Activity.Current?.Id); var invocation = new Nexus.Api.Models.OpenClawInvocationMetadata( invocationContext.IdempotencyKey, invocationContext.CorrelationId, invocationContext.Actor, invocationContext.TraceParent); Response.Headers["X-Correlation-ID"] = invocation.CorrelationId; return Results.Ok(await chat.SendAsync( contextualMessage, conversationId, agentId, invocation, ct)); } catch (ArgumentException exception) { return Results.ValidationProblem(new Dictionary { ["requestMetadata"] = [exception.Message] }); } catch (Exception exception) { logger.LogWarning(exception, "OpenClaw chat request failed for agent {AgentId}", agentId); return Results.Problem( title: "OpenClaw chat unavailable", detail: "The trusted OpenClaw chat endpoint is not enabled or reachable.", statusCode: StatusCodes.Status503ServiceUnavailable); } } }