File: src\Aspire.Hosting\Backchannel\BackchannelDataTypes.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
// These types are source shared between the CLI and the Aspire.Hosting projects.
// The CLI sets the types in its own namespace.
#if CLI
namespace Aspire.Cli.Backchannel;
#else
namespace Aspire.Hosting.Backchannel;
#endif
 
using System.Diagnostics;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
 
// =============================================================================
// Auxiliary Backchannel Contract Rules:
//
// 1. All methods take a single request object (nullable where sensible)
// 2. All methods return a response object (or IAsyncEnumerable<T> for streaming)
// 3. Request/response types are sealed classes with { get; init; } properties
// 4. Required properties use 'required' keyword
// 5. Optional properties are nullable (T?) - can be added without breaking
// 6. Empty request classes are allowed (for future expansion)
// 7. Method names: Get*Async, Watch*Async (streaming), Call*Async (actions)
// =============================================================================
 
#region Capability Constants
 
/// <summary>
/// Constants for auxiliary backchannel capability versions.
/// </summary>
internal static class AuxiliaryBackchannelCapabilities
{
    /// <summary>
    /// Version 1 capabilities (13.1 baseline): GetAppHostInformationAsync, GetDashboardMcpConnectionInfoAsync, StopAppHostAsync.
    /// </summary>
    public const string V1 = "aux.v1";
 
    /// <summary>
    /// Version 2 capabilities (13.2+): Request objects, new methods.
    /// </summary>
    public const string V2 = "aux.v2";
}
 
#endregion
 
#region V2 Request/Response Types
 
/// <summary>
/// Request for getting auxiliary backchannel capabilities.
/// </summary>
internal sealed class GetCapabilitiesRequest { }
 
/// <summary>
/// Response containing auxiliary backchannel capabilities.
/// </summary>
internal sealed class GetCapabilitiesResponse
{
    /// <summary>
    /// Gets the list of supported capability versions (e.g., "aux.v1", "aux.v2").
    /// </summary>
    public required string[] Capabilities { get; init; }
}
 
/// <summary>
/// Request for getting AppHost information.
/// </summary>
internal sealed class GetAppHostInfoRequest { }
 
/// <summary>
/// Response containing AppHost information.
/// </summary>
internal sealed class GetAppHostInfoResponse
{
    /// <summary>
    /// Gets the AppHost process ID.
    /// </summary>
    public required string Pid { get; init; }
 
    /// <summary>
    /// Gets the Aspire hosting version.
    /// </summary>
    public required string AspireHostVersion { get; init; }
 
    /// <summary>
    /// Gets the fully qualified path to the AppHost project.
    /// </summary>
    public required string AppHostPath { get; init; }
 
    /// <summary>
    /// Gets the CLI process ID if the AppHost was launched via the CLI.
    /// </summary>
    public int? CliProcessId { get; init; }
 
    /// <summary>
    /// Gets when the AppHost process started.
    /// </summary>
    public DateTimeOffset? StartedAt { get; init; }
}
 
/// <summary>
/// Request for getting Dashboard information.
/// </summary>
internal sealed class GetDashboardInfoRequest { }
 
/// <summary>
/// Response containing Dashboard information.
/// </summary>
internal sealed class GetDashboardInfoResponse
{
    /// <summary>
    /// Gets the base URL of the Dashboard MCP endpoint.
    /// </summary>
    public string? McpBaseUrl { get; init; }
 
    /// <summary>
    /// Gets the Dashboard MCP API token.
    /// </summary>
    public string? McpApiToken { get; init; }
 
    /// <summary>
    /// Gets the base URL of the Dashboard API (without login token).
    /// Use this for API calls like /api/telemetry/*.
    /// </summary>
    public string? ApiBaseUrl { get; init; }
 
    /// <summary>
    /// Gets the Dashboard API token for authenticated API calls.
    /// </summary>
    public string? ApiToken { get; init; }
 
    /// <summary>
    /// Gets the Dashboard URLs with login tokens.
    /// </summary>
    public required string[] DashboardUrls { get; init; }
 
    /// <summary>
    /// Gets whether the Dashboard is healthy.
    /// </summary>
    public bool IsHealthy { get; init; }
}
 
/// <summary>
/// Request for getting resource snapshots.
/// </summary>
internal sealed class GetResourcesRequest
{
    /// <summary>
    /// Gets an optional filter pattern for resource names.
    /// </summary>
    public string? Filter { get; init; }
}
 
/// <summary>
/// Response containing resource snapshots.
/// </summary>
internal sealed class GetResourcesResponse
{
    /// <summary>
    /// Gets the resource snapshots.
    /// </summary>
    public required ResourceSnapshot[] Resources { get; init; }
}
 
/// <summary>
/// Request for watching resource changes.
/// </summary>
internal sealed class WatchResourcesRequest
{
    /// <summary>
    /// Gets an optional filter pattern for resource names.
    /// </summary>
    public string? Filter { get; init; }
}
 
/// <summary>
/// Request for getting console logs.
/// </summary>
internal sealed class GetConsoleLogsRequest
{
    /// <summary>
    /// Gets the resource name to get logs for.
    /// </summary>
    public required string ResourceName { get; init; }
 
    /// <summary>
    /// Gets whether to follow (stream) new log entries.
    /// </summary>
    public bool Follow { get; init; }
}
 
/// <summary>
/// Request for calling an MCP tool on a resource.
/// </summary>
internal sealed class CallMcpToolRequest
{
    /// <summary>
    /// Gets the resource name.
    /// </summary>
    public required string ResourceName { get; init; }
 
    /// <summary>
    /// Gets the tool name.
    /// </summary>
    public required string ToolName { get; init; }
 
    /// <summary>
    /// Gets the tool arguments.
    /// </summary>
    public JsonElement? Arguments { get; init; }
}
 
/// <summary>
/// Response from calling an MCP tool.
/// </summary>
internal sealed class CallMcpToolResponse
{
    /// <summary>
    /// Gets whether the tool call resulted in an error.
    /// </summary>
    public required bool IsError { get; init; }
 
    /// <summary>
    /// Gets the content items returned by the tool.
    /// </summary>
    public required McpToolContentItem[] Content { get; init; }
}
 
/// <summary>
/// Represents a content item returned by an MCP tool.
/// </summary>
internal sealed class McpToolContentItem
{
    /// <summary>
    /// Gets the content type (e.g., "text").
    /// </summary>
    public required string Type { get; init; }
 
    /// <summary>
    /// Gets the text content.
    /// </summary>
    public string? Text { get; init; }
}
 
/// <summary>
/// Request for stopping the AppHost.
/// </summary>
internal sealed class StopAppHostRequest
{
    /// <summary>
    /// Gets the exit code to use when stopping.
    /// </summary>
    public int? ExitCode { get; init; }
}
 
/// <summary>
/// Response from stopping the AppHost.
/// </summary>
internal sealed class StopAppHostResponse { }
 
/// <summary>
/// Request for executing a resource command.
/// </summary>
internal sealed class ExecuteResourceCommandRequest
{
    /// <summary>
    /// Gets the resource name (or resource ID for replicas).
    /// </summary>
    public required string ResourceName { get; init; }
 
    /// <summary>
    /// Gets the command name (e.g., "resource-start", "resource-stop", "resource-restart").
    /// </summary>
    public required string CommandName { get; init; }
}
 
/// <summary>
/// Response from executing a resource command.
/// </summary>
internal sealed class ExecuteResourceCommandResponse
{
    /// <summary>
    /// Gets whether the command executed successfully.
    /// </summary>
    public required bool Success { get; init; }
 
    /// <summary>
    /// Gets whether the command was canceled.
    /// </summary>
    public bool Canceled { get; init; }
 
    /// <summary>
    /// Gets the error message if the command failed.
    /// </summary>
    public string? ErrorMessage { get; init; }
}
 
#endregion
 
/// <summary>
/// Represents the state of a resource reported via RPC.
/// </summary>
internal sealed class RpcResourceState
{
    /// <summary>
    /// Gets the name of the resource.
    /// </summary>
    public required string Resource { get; init; }
 
    /// <summary>
    /// Gets the type of the resource.
    /// </summary>
    public required string Type { get; init; }
 
    /// <summary>
    /// Gets the state of the resource.
    /// </summary>
    public required string State { get; init; }
 
    /// <summary>
    /// Gets the endpoints associated with the resource.
    /// </summary>
    public required string[] Endpoints { get; init; }
 
    /// <summary>
    /// Gets the health status of the resource.
    /// </summary>
    public string? Health { get; init; }
}
 
/// <summary>
/// Represents dashboard URLs with authentication tokens.
/// </summary>
internal sealed class DashboardUrlsState
{
    public bool DashboardHealthy { get; init; } = true;
 
    /// <summary>
    /// Gets the base dashboard URL with a login token.
    /// </summary>
    public string? BaseUrlWithLoginToken { get; init; }
 
    /// <summary>
    /// Gets the Codespaces dashboard URL with a login token, if available.
    /// </summary>
    public string? CodespacesUrlWithLoginToken { get; init; }
}
 
/// <summary>
/// Envelope for publishing activities sent over the backchannel.
/// </summary>
internal sealed class PublishingActivity
{
    /// <summary>
    /// Gets the type discriminator for the publishing activity.
    /// </summary>
    public required string Type { get; init; }
 
    /// <summary>
    /// Gets the data containing all properties for the publishing activity.
    /// </summary>
    public required PublishingActivityData Data { get; init; }
}
 
/// <summary>
/// Common data for all publishing activities.
/// </summary>
internal sealed class PublishingActivityData
{
    /// <summary>
    /// Gets the unique identifier for the publishing activity.
    /// </summary>
    public required string Id { get; init; }
 
    /// <summary>
    /// Gets the status text describing the publishing activity.
    /// </summary>
    public required string StatusText { get; init; }
 
    /// <summary>
    /// Gets the completion state of the publishing activity.
    /// </summary>
    public string CompletionState { get; init; } = CompletionStates.InProgress;
 
    /// <summary>
    /// Gets a value indicating whether the publishing activity is complete.
    /// </summary>
    public bool IsComplete => CompletionState is not CompletionStates.InProgress;
 
    /// <summary>
    /// Gets a value indicating whether the publishing activity encountered an error.
    /// </summary>
    public bool IsError => CompletionState is CompletionStates.CompletedWithError;
 
    /// <summary>
    /// Gets a value indicating whether the publishing activity completed with warnings.
    /// </summary>
    public bool IsWarning => CompletionState is CompletionStates.CompletedWithWarning;
 
    /// <summary>
    /// Gets the identifier of the step this task belongs to (only applicable for tasks).
    /// </summary>
    public string? StepId { get; init; }
 
    /// <summary>
    /// Gets the optional completion message for tasks (appears as dimmed child text).
    /// </summary>
    public string? CompletionMessage { get; init; }
 
    /// <summary>
    /// Gets the pipeline summary information to display after pipeline completion.
    /// This is a list of key-value pairs with deployment targets, resource names, URLs, etc.
    /// The list preserves the order items were added.
    /// </summary>
    public IReadOnlyList<KeyValuePair<string, string>>? PipelineSummary { get; init; }
 
    /// <summary>
    /// Gets the input information for prompt activities, if available.
    /// </summary>
    public IReadOnlyList<PublishingPromptInput>? Inputs { get; init; }
 
    /// <summary>
    /// Gets the log level for log activities, if available.
    /// </summary>
    public string? LogLevel { get; init; }
 
    /// <summary>
    /// Gets the timestamp for log activities, if available.
    /// </summary>
    public DateTimeOffset? Timestamp { get; init; }
 
    /// <summary>
    /// Gets a value indicating whether markdown formatting is enabled for the publishing activity.
    /// </summary>
    public bool EnableMarkdown { get; init; } = true;
}
 
/// <summary>
/// Represents an input for a publishing prompt.
/// </summary>
internal sealed class PublishingPromptInput
{
    /// <summary>
    /// Gets the name for the input.
    /// Nullable for backwards compatibility with Aspire 9.5 and older app hosts.
    /// </summary>
    public string? Name { get; init; }
 
    /// <summary>
    /// Gets the label for the input.
    /// </summary>
    public required string Label { get; init; }
 
    /// <summary>
    /// Gets the type of the input.
    /// </summary>
    public required string InputType { get; init; }
 
    /// <summary>
    /// Gets a value indicating whether the input is required.
    /// </summary>
    public bool Required { get; init; }
 
    /// <summary>
    /// Gets the options for the input. Only used by select inputs.
    /// </summary>
    public IReadOnlyList<KeyValuePair<string, string>>? Options { get; init; }
 
    /// <summary>
    /// Gets the default value for the input.
    /// </summary>
    public string? Value { get; init; }
 
    /// <summary>
    /// Gets the validation errors for the input.
    /// </summary>
    public IReadOnlyList<string>? ValidationErrors { get; init; }
 
    /// <summary>
    /// Gets or sets a value indicating whether a custom choice is allowed.
    /// </summary>
    public bool AllowCustomChoice { get; init; }
 
    /// <summary>
    /// Gets or sets a value indicating whether the state should be updated when the input value changes.
    /// </summary>
    public bool UpdateStateOnChange { get; init; }
 
    public bool Loading { get; init; }
 
    public bool Disabled { get; init; }
}
 
/// <summary>
/// Constants for publishing activity types.
/// </summary>
internal static class PublishingActivityTypes
{
    public const string Step = "step";
    public const string Task = "task";
    public const string PublishComplete = "publish-complete";
    public const string Prompt = "prompt";
    public const string Log = "log";
}
 
/// <summary>
/// Constants for completion state values.
/// </summary>
internal static class CompletionStates
{
    public const string InProgress = "InProgress";
    public const string Completed = "Completed";
    public const string CompletedWithWarning = "CompletedWithWarning";
    public const string CompletedWithError = "CompletedWithError";
}
 
internal class BackchannelLogEntry
{
    public required EventId EventId { get; set; }
    public required LogLevel LogLevel { get; set; }
    public required string Message { get; set; }
    public required DateTimeOffset Timestamp { get; set; }
    public required string CategoryName { get; set; }
}
 
internal class CommandOutput
{
    public required string Text { get; init; }
    public bool IsErrorMessage { get; init; }
    public int? LineNumber { get; init; }
    /// <summary>
    /// Additional info about type of the message.
    /// Should be used for controlling the display style.
    /// </summary>
    public string? Type { get; init; }
 
    public int? ExitCode { get; init; }
}
 
internal class PublishingPromptInputAnswer
{
    public string? Name { get; set; }
    public string? Value { get; set; }
}
 
/// <summary>
/// Represents the connection information for the Dashboard MCP server.
/// </summary>
internal sealed class DashboardMcpConnectionInfo
{
    /// <summary>
    /// Gets or sets the endpoint URL for the Dashboard MCP server.
    /// </summary>
    public required string EndpointUrl { get; init; }
 
    /// <summary>
    /// Gets or sets the API token for authenticating with the Dashboard MCP server.
    /// </summary>
    public required string ApiToken { get; init; }
}
 
/// <summary>
/// Represents a snapshot of a resource in the application model, suitable for RPC communication.
/// Designed to be extensible - new fields can be added without breaking existing consumers.
/// </summary>
[DebuggerDisplay("Name = {Name}, ResourceType = {ResourceType}, State = {State}, Properties = {Properties.Count}")]
internal sealed class ResourceSnapshot
{
    /// <summary>
    /// Gets the unique name of the resource.
    /// </summary>
    public required string Name { get; init; }
 
    /// <summary>
    /// Gets the display name of the resource.
    /// </summary>
    public string? DisplayName { get; init; }
 
    // ResourceType can't be required because older versions of the backchannel may not set it.
    /// <summary>
    /// Gets the type of the resource (e.g., "Project", "Container", "Executable").
    /// </summary>
    public string? ResourceType { get; init; }
 
    /// <summary>
    /// Gets the type of the resource (e.g., "Project", "Container", "Executable").
    /// </summary>
    [Obsolete("Use ResourceType property instead.")]
    public string? Type
    {
        get => ResourceType;
        init => ResourceType = value;
    }
 
    /// <summary>
    /// Gets the current state of the resource (e.g., "Running", "Stopped", "Starting").
    /// </summary>
    public string? State { get; init; }
 
    /// <summary>
    /// Gets the state style hint (e.g., "success", "error", "warning").
    /// </summary>
    public string? StateStyle { get; init; }
 
    /// <summary>
    /// Gets the health status of the resource (e.g., "Healthy", "Unhealthy", "Degraded").
    /// </summary>
    public string? HealthStatus { get; init; }
 
    /// <summary>
    /// Gets the exit code if the resource has exited.
    /// </summary>
    public int? ExitCode { get; init; }
 
    /// <summary>
    /// Gets the creation timestamp of the resource.
    /// </summary>
    public DateTimeOffset? CreatedAt { get; init; }
 
    /// <summary>
    /// Gets the start timestamp of the resource.
    /// </summary>
    public DateTimeOffset? StartedAt { get; init; }
 
    /// <summary>
    /// Gets the stop timestamp of the resource.
    /// </summary>
    public DateTimeOffset? StoppedAt { get; init; }
 
    /// <summary>
    /// Gets the URLs exposed by this resource.
    /// </summary>
    public ResourceSnapshotUrl[] Urls { get; init; } = [];
 
    /// <summary>
    /// Gets the relationships to other resources.
    /// </summary>
    public ResourceSnapshotRelationship[] Relationships { get; init; } = [];
 
    /// <summary>
    /// Gets the health reports for this resource.
    /// </summary>
    public ResourceSnapshotHealthReport[] HealthReports { get; init; } = [];
 
    /// <summary>
    /// Gets the volumes mounted to this resource.
    /// </summary>
    public ResourceSnapshotVolume[] Volumes { get; init; } = [];
 
    /// <summary>
    /// Gets the environment variables for this resource.
    /// </summary>
    public ResourceSnapshotEnvironmentVariable[] EnvironmentVariables { get; init; } = [];
 
    /// <summary>
    /// Gets additional properties as key-value pairs.
    /// This allows for extensibility without changing the schema.
    /// </summary>
    public Dictionary<string, string?> Properties { get; init; } = [];
 
    /// <summary>
    /// Gets the MCP server information if the resource exposes an MCP endpoint.
    /// </summary>
    public ResourceSnapshotMcpServer? McpServer { get; init; }
 
    /// <summary>
    /// Gets the commands available for this resource.
    /// </summary>
    public ResourceSnapshotCommand[] Commands { get; init; } = [];
}
 
/// <summary>
/// Represents a command available for a resource.
/// </summary>
[DebuggerDisplay("Name = {Name}, State = {State}")]
internal sealed class ResourceSnapshotCommand
{
    /// <summary>
    /// Gets the command name (e.g., "resource-start", "resource-stop", "resource-restart").
    /// </summary>
    public required string Name { get; init; }
 
    /// <summary>
    /// Gets the display name of the command.
    /// </summary>
    public string? DisplayName { get; init; }
 
    /// <summary>
    /// Gets the description of the command.
    /// </summary>
    public string? Description { get; init; }
 
    /// <summary>
    /// Gets the state of the command (e.g., "Enabled", "Disabled", "Hidden").
    /// </summary>
    public required string State { get; init; }
}
 
/// <summary>
/// Represents a URL exposed by a resource.
/// </summary>
[DebuggerDisplay("Name = {Name}, Url = {Url}")]
internal sealed class ResourceSnapshotUrl
{
    /// <summary>
    /// Gets the URL name (e.g., "http", "https", "tcp").
    /// </summary>
    public required string Name { get; init; }
 
    /// <summary>
    /// Gets the full URL including scheme, host, and port.
    /// </summary>
    public required string Url { get; init; }
 
    /// <summary>
    /// Gets whether this is an internal URL.
    /// </summary>
    public bool IsInternal { get; init; }
 
    /// <summary>
    /// Gets the display properties for the URL.
    /// </summary>
    public ResourceSnapshotUrlDisplayProperties? DisplayProperties { get; init; }
}
 
/// <summary>
/// Represents display properties for a URL.
/// </summary>
[DebuggerDisplay("DisplayName = {DisplayName}, SortOrder = {SortOrder}")]
internal sealed class ResourceSnapshotUrlDisplayProperties
{
    /// <summary>
    /// Gets the display name of the URL.
    /// </summary>
    public string? DisplayName { get; init; }
 
    /// <summary>
    /// Gets the sort order for display. Higher numbers are displayed first.
    /// </summary>
    public int SortOrder { get; init; }
}
 
/// <summary>
/// Represents a relationship to another resource.
/// </summary>
[DebuggerDisplay("ResourceName = {ResourceName}, Type = {Type}")]
internal sealed class ResourceSnapshotRelationship
{
    /// <summary>
    /// Gets the name of the related resource.
    /// </summary>
    public required string ResourceName { get; init; }
 
    /// <summary>
    /// Gets the relationship type (e.g., "Parent", "Reference").
    /// </summary>
    public required string Type { get; init; }
}
 
/// <summary>
/// Represents a health report for a resource.
/// </summary>
[DebuggerDisplay("Name = {Name}, Status = {Status}")]
internal sealed class ResourceSnapshotHealthReport
{
    /// <summary>
    /// Gets the name of the health check.
    /// </summary>
    public required string Name { get; init; }
 
    /// <summary>
    /// Gets the status (e.g., "Healthy", "Unhealthy", "Degraded").
    /// </summary>
    public string? Status { get; init; }
 
    /// <summary>
    /// Gets the description of the health report.
    /// </summary>
    public string? Description { get; init; }
 
    /// <summary>
    /// Gets the exception text if the health check failed.
    /// </summary>
    public string? ExceptionText { get; init; }
}
 
/// <summary>
/// Represents a volume mounted to a resource.
/// </summary>
[DebuggerDisplay("Source = {Source}, Target = {Target}")]
internal sealed class ResourceSnapshotVolume
{
    /// <summary>
    /// Gets the source path or volume name.
    /// </summary>
    public string? Source { get; init; }
 
    /// <summary>
    /// Gets the target path in the container.
    /// </summary>
    public required string Target { get; init; }
 
    /// <summary>
    /// Gets the mount type (e.g., "bind", "volume").
    /// </summary>
    public required string MountType { get; init; }
 
    /// <summary>
    /// Gets whether the volume is read-only.
    /// </summary>
    public bool IsReadOnly { get; init; }
}
 
/// <summary>
/// Represents an environment variable for a resource.
/// </summary>
[DebuggerDisplay("Name = {Name}, Value = {Value}")]
internal sealed class ResourceSnapshotEnvironmentVariable
{
    /// <summary>
    /// Gets the name of the environment variable.
    /// </summary>
    public required string Name { get; init; }
 
    /// <summary>
    /// Gets the value of the environment variable.
    /// </summary>
    public string? Value { get; init; }
 
    /// <summary>
    /// Gets whether this environment variable is from the resource specification.
    /// </summary>
    public bool IsFromSpec { get; init; }
}
 
/// <summary>
/// Represents MCP server information for a resource.
/// </summary>
[DebuggerDisplay("EndpointUrl = {EndpointUrl}")]
internal sealed class ResourceSnapshotMcpServer
{
    /// <summary>
    /// Gets the MCP endpoint URL.
    /// </summary>
    public required string EndpointUrl { get; init; }
 
    /// <summary>
    /// Gets the tools exposed by the MCP server.
    /// </summary>
    public required Tool[] Tools { get; init; }
}
 
/// <summary>
/// Represents information about the AppHost for the MCP server.
/// </summary>
internal sealed class AppHostInformation
{
    /// <summary>
    /// Gets or sets the fully qualified path to the AppHost project.
    /// </summary>
    public required string AppHostPath { get; init; }
 
    /// <summary>
    /// Gets or sets the process ID of the AppHost.
    /// </summary>
    public required int ProcessId { get; init; }
 
    /// <summary>
    /// Gets or sets the process ID of the CLI that launched the AppHost, if applicable.
    /// This value is only set when the AppHost is launched via the Aspire CLI.
    /// </summary>
    public int? CliProcessId { get; init; }
 
    /// <summary>
    /// Gets or sets when the AppHost process started.
    /// </summary>
    public DateTimeOffset? StartedAt { get; init; }
}
 
/// <summary>
/// Represents a log line from a resource's console output.
/// </summary>
internal sealed class ResourceLogLine
{
    /// <summary>
    /// Gets the name of the resource that produced this log line.
    /// </summary>
    public required string ResourceName { get; init; }
 
    /// <summary>
    /// Gets the line number within the log stream.
    /// </summary>
    public required int LineNumber { get; init; }
 
    /// <summary>
    /// Gets the content of the log line.
    /// </summary>
    public required string Content { get; init; }
 
    /// <summary>
    /// Gets whether this log line is from stderr (error output).
    /// </summary>
    public bool IsError { get; init; }
}