using System.Text.Encodings.Web; using System.Text.Json; namespace NucleicProtocol.Interop; /// Small typed constructors for the renderer's first intents. Swift remains the decoder and /// source of truth; these helpers only keep hand-written UI JSON out of call sites. public static class ClientIntents { public static string ListSessions() => TagOnly("listSessions"); public static string ListDashboard() => TagOnly("listDashboard"); public static string ListPeers() => TagOnly("listPeers"); public static string Ping() => TagOnly("ping"); public static string Subscribe(string sessionId, ulong? sinceSequence = null, string verbosity = "full") => Write(writer => { writer.WriteString("sessionID", sessionId); if (sinceSequence is { } sequence) writer.WriteNumber("sinceSeq", sequence); writer.WriteString("t", "subscribe"); writer.WriteString("verbosity", verbosity); }); public static string Unsubscribe(string sessionId) => Write(writer => { writer.WriteString("sessionID", sessionId); writer.WriteString("t", "unsubscribe"); }); public static string ApprovalAllow(string approvalId, JsonElement? updatedInput = null) => ApprovalRespond(approvalId, "allow", updatedInput: updatedInput); public static string ApprovalDeny(string approvalId, string? reason = null) => ApprovalRespond(approvalId, "deny", reason: reason); public static string ApprovalAllowAlways(string approvalId, ApprovalAlwaysScope scope) => ApprovalRespond(approvalId, "allowAlways", scope: ScopeValue(scope)); public static string ApprovalCancelRun(string approvalId) => ApprovalRespond(approvalId, "cancelRun"); public static string SendInput(string sessionId, string text) => Write(writer => { writer.WritePropertyName("input"); writer.WriteStartObject(); writer.WritePropertyName("parts"); writer.WriteStartArray(); writer.WriteStartObject(); writer.WriteString("text", text); writer.WriteString("type", "text"); writer.WriteEndObject(); writer.WriteEndArray(); writer.WriteEndObject(); writer.WriteString("sessionID", sessionId); writer.WriteString("t", "sendInput"); }); private static string TagOnly(string tag) => JsonSerializer.Serialize( new TagOnlyIntent(tag), ProtocolJsonContext.Default.TagOnlyIntent); private static string ApprovalRespond( string approvalId, string decisionType, JsonElement? updatedInput = null, string? scope = null, string? reason = null) => Write(writer => { writer.WriteString("approvalID", approvalId); writer.WritePropertyName("decision"); writer.WriteStartObject(); writer.WriteString("type", decisionType); if (updatedInput is { } input) { writer.WritePropertyName("updatedInput"); input.WriteTo(writer); } if (scope is not null) writer.WriteString("scope", scope); if (reason is not null) writer.WriteString("reason", reason); writer.WriteEndObject(); writer.WriteString("t", "approvalRespond"); }); private static string ScopeValue(ApprovalAlwaysScope scope) => scope switch { ApprovalAlwaysScope.Session => "session", ApprovalAlwaysScope.ToolName => "toolName", ApprovalAlwaysScope.ToolNameWithPattern => "toolNameWithPattern", _ => throw new ArgumentOutOfRangeException(nameof(scope)), }; private static string Write(Action body) { using var stream = new MemoryStream(); using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, })) { writer.WriteStartObject(); body(writer); writer.WriteEndObject(); } return System.Text.Encoding.UTF8.GetString(stream.GetBuffer(), 0, checked((int)stream.Length)); } } public enum ApprovalAlwaysScope { Session, ToolName, ToolNameWithPattern, }