// // Copyright (c) StellaOps. Licensed under the BUSL-1.1. // using StellaOps.AdvisoryAI.Chat; using System.Collections.Immutable; using System.Text.Json.Serialization; namespace StellaOps.AdvisoryAI.WebService.Contracts; /// /// Request to create a new conversation. /// Sprint: SPRINT_20260107_006_003 Task CH-005 /// public sealed record CreateConversationRequest { /// /// Gets the tenant identifier. /// [JsonPropertyName("tenantId")] public required string TenantId { get; init; } /// /// Gets the optional initial context for the conversation. /// [JsonPropertyName("context")] public ConversationContextRequest? Context { get; init; } /// /// Gets optional metadata key-value pairs. /// [JsonPropertyName("metadata")] public IReadOnlyDictionary? Metadata { get; init; } } /// /// Request for conversation context initialization. /// public sealed record ConversationContextRequest { /// /// Gets the current CVE ID being discussed. /// [JsonPropertyName("currentCveId")] public string? CurrentCveId { get; init; } /// /// Gets the current component PURL. /// [JsonPropertyName("currentComponent")] public string? CurrentComponent { get; init; } /// /// Gets the current image digest. /// [JsonPropertyName("currentImageDigest")] public string? CurrentImageDigest { get; init; } /// /// Gets the scan ID in context. /// [JsonPropertyName("scanId")] public string? ScanId { get; init; } /// /// Gets the SBOM ID in context. /// [JsonPropertyName("sbomId")] public string? SbomId { get; init; } } /// /// Request to add a turn to an existing conversation. /// public sealed record AddTurnRequest { /// /// Gets the user message content. /// [JsonPropertyName("content")] public required string Content { get; init; } /// /// Gets optional metadata for this turn. /// [JsonPropertyName("metadata")] public IReadOnlyDictionary? Metadata { get; init; } /// /// Gets whether to stream the response as Server-Sent Events. /// [JsonPropertyName("stream")] public bool Stream { get; init; } = false; } /// /// Response for a created conversation. /// public sealed record ConversationResponse { /// /// Gets the conversation ID. /// [JsonPropertyName("conversationId")] public required string ConversationId { get; init; } /// /// Gets the tenant ID. /// [JsonPropertyName("tenantId")] public required string TenantId { get; init; } /// /// Gets the user ID. /// [JsonPropertyName("userId")] public required string UserId { get; init; } /// /// Gets the creation timestamp. /// [JsonPropertyName("createdAt")] public required DateTimeOffset CreatedAt { get; init; } /// /// Gets the last update timestamp. /// [JsonPropertyName("updatedAt")] public required DateTimeOffset UpdatedAt { get; init; } /// /// Gets the conversation turns. /// [JsonPropertyName("turns")] public required IReadOnlyList Turns { get; init; } /// /// Creates a response from a conversation. /// public static ConversationResponse FromConversation(Conversation conversation) => new() { ConversationId = conversation.ConversationId, TenantId = conversation.TenantId, UserId = conversation.UserId, CreatedAt = conversation.CreatedAt, UpdatedAt = conversation.UpdatedAt, Turns = conversation.Turns.Select(ConversationTurnResponse.FromTurn).ToList() }; } /// /// Response for a conversation turn. /// public sealed record ConversationTurnResponse { /// /// Gets the turn ID. /// [JsonPropertyName("turnId")] public required string TurnId { get; init; } /// /// Gets the role (user, assistant, system). /// [JsonPropertyName("role")] public required string Role { get; init; } /// /// Gets the message content. /// [JsonPropertyName("content")] public required string Content { get; init; } /// /// Gets the timestamp. /// [JsonPropertyName("timestamp")] public required DateTimeOffset Timestamp { get; init; } /// /// Gets the evidence links in this turn. /// [JsonPropertyName("evidenceLinks")] public IReadOnlyList? EvidenceLinks { get; init; } /// /// Gets the proposed actions in this turn. /// [JsonPropertyName("proposedActions")] public IReadOnlyList? ProposedActions { get; init; } /// /// Creates a response from a turn. /// public static ConversationTurnResponse FromTurn(ConversationTurn turn) => new() { TurnId = turn.TurnId, Role = turn.Role.ToString().ToLowerInvariant(), Content = turn.Content, Timestamp = turn.Timestamp, EvidenceLinks = turn.EvidenceLinks.IsEmpty ? null : turn.EvidenceLinks.Select(EvidenceLinkResponse.FromLink).ToList(), ProposedActions = turn.ProposedActions.IsEmpty ? null : turn.ProposedActions.Select(ProposedActionResponse.FromAction).ToList() }; } /// /// Response for an evidence link. /// public sealed record EvidenceLinkResponse { /// /// Gets the link type (sbom, dsse, callGraph, reachability, etc.). /// [JsonPropertyName("type")] public required string Type { get; init; } /// /// Gets the URI. /// [JsonPropertyName("uri")] public required string Uri { get; init; } /// /// Gets the display label. /// [JsonPropertyName("label")] public string? Label { get; init; } /// /// Gets the confidence score. /// [JsonPropertyName("confidence")] public double? Confidence { get; init; } /// /// Creates a response from an evidence link. /// public static EvidenceLinkResponse FromLink(EvidenceLink link) => new() { Type = link.Type.ToString(), Uri = link.Uri, Label = link.Label, Confidence = link.Confidence }; } /// /// Response for a proposed action. /// public sealed record ProposedActionResponse { /// /// Gets the action type (approve, quarantine, defer, generate_manifest, create_vex). /// [JsonPropertyName("actionType")] public required string ActionType { get; init; } /// /// Gets the action label. /// [JsonPropertyName("label")] public required string Label { get; init; } /// /// Gets the policy gate for this action. /// [JsonPropertyName("policyGate")] public string? PolicyGate { get; init; } /// /// Gets whether this action requires confirmation. /// [JsonPropertyName("requiresConfirmation")] public bool RequiresConfirmation { get; init; } /// /// Creates a response from a proposed action. /// public static ProposedActionResponse FromAction(ProposedAction action) => new() { ActionType = action.ActionType, Label = action.Label, PolicyGate = action.PolicyGate, RequiresConfirmation = action.RequiresConfirmation }; } /// /// Response for the assistant's turn (non-streaming). /// public sealed record AssistantTurnResponse { /// /// Gets the turn ID. /// [JsonPropertyName("turnId")] public required string TurnId { get; init; } /// /// Gets the assistant's response content. /// [JsonPropertyName("content")] public required string Content { get; init; } /// /// Gets the timestamp. /// [JsonPropertyName("timestamp")] public required DateTimeOffset Timestamp { get; init; } /// /// Gets evidence links found in the response. /// [JsonPropertyName("evidenceLinks")] public IReadOnlyList? EvidenceLinks { get; init; } /// /// Gets proposed actions in the response. /// [JsonPropertyName("proposedActions")] public IReadOnlyList? ProposedActions { get; init; } /// /// Gets the grounding score (0.0-1.0). /// [JsonPropertyName("groundingScore")] public double GroundingScore { get; init; } /// /// Gets the token count. /// [JsonPropertyName("tokenCount")] public int TokenCount { get; init; } /// /// Gets the processing duration in milliseconds. /// [JsonPropertyName("durationMs")] public long DurationMs { get; init; } } /// /// Response for listing conversations. /// public sealed record ConversationListResponse { /// /// Gets the conversations. /// [JsonPropertyName("conversations")] public required IReadOnlyList Conversations { get; init; } /// /// Gets the total count. /// [JsonPropertyName("totalCount")] public int TotalCount { get; init; } } /// /// Summary of a conversation for listing. /// public sealed record ConversationSummary { /// /// Gets the conversation ID. /// [JsonPropertyName("conversationId")] public required string ConversationId { get; init; } /// /// Gets the creation timestamp. /// [JsonPropertyName("createdAt")] public required DateTimeOffset CreatedAt { get; init; } /// /// Gets the last update timestamp. /// [JsonPropertyName("updatedAt")] public required DateTimeOffset UpdatedAt { get; init; } /// /// Gets the turn count. /// [JsonPropertyName("turnCount")] public int TurnCount { get; init; } /// /// Gets a preview of the first user message. /// [JsonPropertyName("preview")] public string? Preview { get; init; } }