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