Initial commit
Build & Deploy / build (push) Failing after 1m23s

This commit is contained in:
2026-07-14 19:09:58 +02:00
commit 96fde6a099
43 changed files with 6913 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
namespace ReverseLlama.Protocol;
public sealed class HeaderPair
{
public HeaderPair()
{
}
public HeaderPair(string name, string value)
{
Name = name;
Value = value;
}
public string Name { get; set; } = "";
public string Value { get; set; } = "";
}
@@ -0,0 +1,10 @@
namespace ReverseLlama.Protocol;
public static class ProtocolConstants
{
public const string DefaultStatusPath = "/_reverse-llama/status";
public const string DefaultTunnelPath = "/_reverse-llama/tunnel";
public const string TokenHeader = "X-Reverse-Llama-Token";
public const string ClientIdHeader = "X-Reverse-Llama-Client-Id";
public const string ReplacedCloseDescription = "reverse-llama-replaced";
}
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,34 @@
namespace ReverseLlama.Protocol;
public sealed class TunnelMessage
{
public string Type { get; set; } = "";
public string RequestId { get; set; } = "";
public string? Method { get; set; }
public string? PathAndQuery { get; set; }
public bool HasBody { get; set; }
public List<HeaderPair> Headers { get; set; } = [];
public int? StatusCode { get; set; }
public string? ReasonPhrase { get; set; }
public byte[]? Body { get; set; }
public List<string> Models { get; set; } = [];
public List<string> ActiveModels { get; set; } = [];
public string? Command { get; set; }
public string? Model { get; set; }
public string? PayloadJson { get; set; }
public string? Error { get; set; }
}
@@ -0,0 +1,16 @@
namespace ReverseLlama.Protocol;
public static class TunnelMessageTypes
{
public const string HttpRequest = "http.request";
public const string HttpRequestBody = "http.request.body";
public const string HttpRequestComplete = "http.request.complete";
public const string HttpResponseHeaders = "http.response.headers";
public const string HttpResponseBody = "http.response.body";
public const string HttpResponseComplete = "http.response.complete";
public const string ModelSnapshot = "models.snapshot";
public const string ModelCommand = "models.command";
public const string ModelCommandResult = "models.command.result";
public const string Cancel = "cancel";
public const string Error = "error";
}
@@ -0,0 +1,57 @@
using System.Net.WebSockets;
using System.Text.Json;
namespace ReverseLlama.Protocol;
public static class WebSocketMessageTransport
{
private const int BufferSize = 64 * 1024;
private const int MaxMessageSize = 128 * 1024 * 1024;
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
public static async Task SendAsync(WebSocket socket, TunnelMessage message, CancellationToken cancellationToken)
{
var payload = JsonSerializer.SerializeToUtf8Bytes(message, JsonOptions);
await socket.SendAsync(payload, WebSocketMessageType.Text, true, cancellationToken);
}
public static async Task<TunnelMessage?> ReceiveAsync(WebSocket socket, CancellationToken cancellationToken)
{
var buffer = new byte[BufferSize];
using var payload = new MemoryStream();
while (true)
{
var result = await socket.ReceiveAsync(buffer, cancellationToken);
if (result.MessageType == WebSocketMessageType.Close)
{
return null;
}
if (result.MessageType != WebSocketMessageType.Text)
{
throw new InvalidOperationException("Only text WebSocket messages are supported.");
}
if (result.Count > 0)
{
payload.Write(buffer.AsSpan(0, result.Count));
}
if (payload.Length > MaxMessageSize)
{
throw new InvalidOperationException($"Tunnel message exceeded {MaxMessageSize} bytes.");
}
if (result.EndOfMessage)
{
break;
}
}
payload.Position = 0;
var message = await JsonSerializer.DeserializeAsync<TunnelMessage>(payload, JsonOptions, cancellationToken);
return message ?? throw new InvalidOperationException("Received an empty tunnel message.");
}
}