Files
nucleic-windows/NucleicBroker/IWslc.cs
T

116 lines
5.7 KiB
C#
Raw Normal View History

2026-07-27 22:02:34 -07:00
using System.Text.Json.Serialization;
namespace NucleicBroker;
/// <summary>
/// The one seam between broker logic and Microsoft.WSL.Containers (docs/WINDOWS_PORT.md
/// §3.3): everything the RPC surface needs from wslc, and nothing WinRT. The real facade
/// (Wslc/WslcFacade.cs, compiled with -p:UseWslc=true) adapts the preview API; unit tests
/// substitute FakeWslc; non-Windows builds get UnavailableWslc. Async events (process stdio,
/// pull progress, session death) flow out through <see cref="IBrokerEvents"/> so no wslc
/// event thread ever blocks on the stdio pipe.
/// </summary>
public interface IWslc
{
/// <summary>Reported in the `hello` capabilities exchange; null when wslc is absent.</summary>
string? WslcVersion { get; }
/// <summary>Install the sink BEFORE any operation that can emit events.</summary>
void SetEvents(IBrokerEvents events);
/// <summary>Missing OS components (WSL, the container service, …), empty when ready.</summary>
Task<IReadOnlyList<string>> MissingComponentsAsync(CancellationToken ct);
/// <summary>Install missing components; progress via <see cref="IBrokerEvents.InstallProgress"/>.</summary>
Task InstallComponentsAsync(CancellationToken ct);
/// <summary>Create-or-attach the per-channel wslc session; returns the WSL-facing host
/// gateway address guests reach the host on (docs/WINDOWS_PORT.md §5).</summary>
Task<string> EnsureSessionAsync(SessionSpec spec, CancellationToken ct);
Task TerminateSessionAsync(CancellationToken ct);
/// <summary>Pull an OCI image; progress via <see cref="IBrokerEvents.PullProgress"/>.</summary>
Task PullImageAsync(string reference, RegistryAuth? auth, CancellationToken ct);
Task<IReadOnlyList<ImageInfo>> ListImagesAsync(CancellationToken ct);
Task DeleteImageAsync(string reference, CancellationToken ct);
/// <summary>Digest/size for a local image, or null when not present.</summary>
Task<ImageInfo?> InspectImageAsync(string reference, CancellationToken ct);
Task CreateContainerAsync(ContainerCreateSpec spec, CancellationToken ct);
Task StartContainerAsync(string name, CancellationToken ct);
Task StopContainerAsync(string name, int signal, int graceMs, CancellationToken ct);
Task DeleteContainerAsync(string name, bool force, CancellationToken ct);
Task<IReadOnlyList<ContainerInfo>> ListContainersAsync(CancellationToken ct);
/// <summary>One of "running" | "stopped" | "absent" (kept coarse on purpose — the Swift
/// policy layer only distinguishes these three).</summary>
Task<string> ContainerStateAsync(string name, CancellationToken ct);
/// <summary>cgroup counters for the resource monitor; null when not running.</summary>
Task<ContainerStatsInfo?> ContainerStatsAsync(string name, CancellationToken ct);
/// <summary>Start a process in a running container. `procId` is minted by the broker and
/// keys every event this process emits through <see cref="IBrokerEvents"/>.</summary>
Task<IWslcProcess> ExecAsync(long procId, ProcSpec spec, CancellationToken ct);
}
/// <summary>Control half of a running in-container process (output arrives via events).</summary>
public interface IWslcProcess
{
Task WriteStdinAsync(ReadOnlyMemory<byte> data, CancellationToken ct);
Task CloseStdinAsync(CancellationToken ct);
Task SignalAsync(int signal, CancellationToken ct);
/// <summary>tty mode only (the Terminal panel); no-op for pipe-mode processes.</summary>
Task ResizeAsync(int cols, int rows, CancellationToken ct);
}
/// <summary>Event sink the broker hands to the facade; implementations must be
/// non-blocking (they enqueue onto the outbound writer).</summary>
public interface IBrokerEvents
{
void ProcOutput(long procId, bool stderr, ReadOnlySpan<byte> chunk);
void ProcExited(long procId, int code);
void SessionDown(string reason);
void PullProgress(string reference, string status, long current, long total);
void InstallProgress(string status, double percent);
}
/// <summary>A structured facade failure, surfaced to hostd as JSON-RPC error -32000 with
/// `data.kind` so the Swift side can branch (e.g. `brokerLost` vs `imagePullFailed`).</summary>
public sealed class WslcError(string kind, string message) : Exception(message)
{
public string Kind { get; } = kind;
public const string Unavailable = "wslc_unavailable";
public const string NotFound = "not_found";
public const string NotRunning = "not_running";
public const string PullFailed = "image_pull_failed";
public const string StartFailed = "start_failed";
public const string AiUnavailable = "ai_unavailable";
}
// DTOs — property names (after camel-casing) match the §3.3 wire keys exactly.
public sealed record SessionSpec(
string Name, string DataDir, int? Cpu, long? MemoryMB);
public sealed record RegistryAuth(string? Username, string? Password);
public sealed record ImageInfo(
[property: JsonPropertyName("ref")] string Ref, string? Digest, long? SizeBytes);
public sealed record VolumeSpec(
string Host, string Guest, [property: JsonPropertyName("ro")] bool ReadOnly);
public sealed record ContainerCreateSpec(
string Name, string Image, IReadOnlyList<VolumeSpec>? Volumes,
string? NetworkingMode, string? Hostname,
IReadOnlyDictionary<string, string>? Env, IReadOnlyList<string>? InitArgv);
public sealed record ContainerInfo(string Name, string Image, string State);
public sealed record ContainerStatsInfo(
long CpuUsageUsec, long MemoryUsedBytes, long MemoryLimitBytes, long? OomKills);
public sealed record ProcSpec(
string Container, IReadOnlyList<string> Argv,
IReadOnlyDictionary<string, string>? Env, string? Cwd,
int? Uid, int? Gid, bool Tty);