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( diff --git a/CodingAgentExplorer/appsettings.json b/CodingAgentExplorer/appsettings.json index 8cf8985..1d18462 100644 --- a/CodingAgentExplorer/appsettings.json +++ b/CodingAgentExplorer/appsettings.json @@ -20,7 +20,7 @@ "anthropic-cluster": { "Destinations": { "anthropic-api": { - "Address": "https://api.anthropic.com" + "Address": "http://localhost:1234" } }, "HttpRequest": { 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

| + + |