From bf7d347c1f5a9fa739367b425b279a0b9db2641a Mon Sep 17 00:00:00 2001 From: Marek Kacprzak Date: Sat, 11 Jul 2026 14:57:04 +0200 Subject: [PATCH 1/3] Refactor to work with local model like LLMStudio - neet to change only appsettings.json - anthropic-cluster anthropic-api url to http://localhost:1234 --- .../Proxy/CaptureTransformProvider.cs | 25 +++++++++++++ .../Proxy/DynamicProxyConfigProvider.cs | 37 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/CodingAgentExplorer/Proxy/CaptureTransformProvider.cs b/CodingAgentExplorer/Proxy/CaptureTransformProvider.cs index 08eeeb1..9eb5a91 100644 --- a/CodingAgentExplorer/Proxy/CaptureTransformProvider.cs +++ b/CodingAgentExplorer/Proxy/CaptureTransformProvider.cs @@ -193,6 +193,18 @@ private static async Task HandleSseStreamingAsync( foreach (var header in proxyResponse.Headers .Where(header => !clientResponse.Headers.ContainsKey(header.Key))) { + if (IsHopByHopHeader(header.Key)) + continue; + + clientResponse.Headers[header.Key] = header.Value.ToArray(); + } + + foreach (var header in proxyResponse.Content.Headers + .Where(header => !clientResponse.Headers.ContainsKey(header.Key))) + { + if (IsHopByHopHeader(header.Key)) + continue; + clientResponse.Headers[header.Key] = header.Value.ToArray(); } @@ -248,6 +260,19 @@ private static async Task HandleSseStreamingAsync( proxiedRequest.DurationMs = stopwatch.Elapsed.TotalMilliseconds; } + private static bool IsHopByHopHeader(string headerName) + { + return headerName.Equals("Connection", StringComparison.OrdinalIgnoreCase) + || headerName.Equals("Keep-Alive", StringComparison.OrdinalIgnoreCase) + || headerName.Equals("Proxy-Authenticate", StringComparison.OrdinalIgnoreCase) + || headerName.Equals("Proxy-Authorization", StringComparison.OrdinalIgnoreCase) + || headerName.Equals("TE", StringComparison.OrdinalIgnoreCase) + || headerName.Equals("Trailer", StringComparison.OrdinalIgnoreCase) + || headerName.Equals("Transfer-Encoding", StringComparison.OrdinalIgnoreCase) + || headerName.Equals("Upgrade", StringComparison.OrdinalIgnoreCase) + || headerName.Equals("Content-Length", StringComparison.OrdinalIgnoreCase); + } + private static async Task HandleNonStreamingResponseAsync( HttpResponseMessage proxyResponse, ProxiedRequest proxiedRequest, Stopwatch stopwatch, bool isMcp = false) diff --git a/CodingAgentExplorer/Proxy/DynamicProxyConfigProvider.cs b/CodingAgentExplorer/Proxy/DynamicProxyConfigProvider.cs index 348b241..cd07d3a 100644 --- a/CodingAgentExplorer/Proxy/DynamicProxyConfigProvider.cs +++ b/CodingAgentExplorer/Proxy/DynamicProxyConfigProvider.cs @@ -5,7 +5,10 @@ namespace CodingAgentExplorer.Proxy; -public class DynamicProxyConfigProvider(McpProxyConfig mcpConfig) : IProxyConfigProvider +public class DynamicProxyConfigProvider( + McpProxyConfig mcpConfig, + IConfiguration configuration, + ILogger logger) : IProxyConfigProvider { private static readonly ForwarderRequestConfig LongTimeout = new() { @@ -15,6 +18,8 @@ public class DynamicProxyConfigProvider(McpProxyConfig mcpConfig) : IProxyConfig public IProxyConfig GetConfig() { + var anthropicDestination = ResolveAnthropicDestination(configuration, logger); + var routes = new List { new() @@ -36,7 +41,7 @@ public IProxyConfig GetConfig() ClusterId = "anthropic-cluster", Destinations = new Dictionary { - ["dest"] = new() { Address = "https://api.anthropic.com" } + ["dest"] = new() { Address = anthropicDestination } }, HttpRequest = LongTimeout } @@ -75,6 +80,34 @@ public IProxyConfig GetConfig() return new InMemoryProxyConfig(routes, clusters, mcpConfig.GetChangeToken()); } + + private static string ResolveAnthropicDestination( + IConfiguration configuration, + ILogger logger) + { + // Allow env var override for convenience in local setups. + var configured = Environment.GetEnvironmentVariable("CODING_AGENT_EXPLORER_UPSTREAM_URL"); + + if (string.IsNullOrWhiteSpace(configured)) + { + configured = configuration + .GetSection("ReverseProxy:Clusters:anthropic-cluster:Destinations") + .GetChildren() + .Select(d => d["Address"]) + .FirstOrDefault(a => !string.IsNullOrWhiteSpace(a)); + } + + if (!string.IsNullOrWhiteSpace(configured) + && Uri.TryCreate(configured, UriKind.Absolute, out var uri) + && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)) + { + return uri.GetLeftPart(UriPartial.Authority); + } + + logger.LogWarning( + "No valid upstream destination configured. Falling back to https://api.anthropic.com"); + return "https://api.anthropic.com"; + } } internal sealed class InMemoryProxyConfig( From 13ed64fe492d113e935505c581562952d90ab3c9 Mon Sep 17 00:00:00 2001 From: Marek Kacprzak Date: Sat, 11 Jul 2026 15:22:27 +0200 Subject: [PATCH 2/3] Add light colour merge from (https://github.com/boriselec/CodingAgentExplorer/tree/boriselec/1) --- .../wwwroot/conversation/index.html | 4 + .../wwwroot/css/conversation.css | 44 ++++------- CodingAgentExplorer/wwwroot/css/styles.css | 20 +---- CodingAgentExplorer/wwwroot/css/theme.css | 74 +++++++++++++++++++ CodingAgentExplorer/wwwroot/index.html | 16 +--- .../wwwroot/inspector/index.html | 4 + CodingAgentExplorer/wwwroot/js/theme.js | 23 ++++++ CodingAgentExplorer/wwwroot/mcp/index.html | 21 ++---- 8 files changed, 133 insertions(+), 73 deletions(-) create mode 100644 CodingAgentExplorer/wwwroot/css/theme.css create mode 100644 CodingAgentExplorer/wwwroot/js/theme.js diff --git a/CodingAgentExplorer/wwwroot/conversation/index.html b/CodingAgentExplorer/wwwroot/conversation/index.html index d193d1e..bb2dd6d 100644 --- a/CodingAgentExplorer/wwwroot/conversation/index.html +++ b/CodingAgentExplorer/wwwroot/conversation/index.html @@ -4,7 +4,9 @@ Coding Agent Explorer - Conversation View + +
@@ -23,6 +25,8 @@

Conversation View

| + + |