diff --git a/internal/api/modules/amp/fallback_handlers.go b/internal/api/modules/amp/fallback_handlers.go index d0ccac56..d8c140ad 100644 --- a/internal/api/modules/amp/fallback_handlers.go +++ b/internal/api/modules/amp/fallback_handlers.go @@ -75,6 +75,17 @@ func (fh *FallbackHandler) WrapHandler(handler gin.HandlerFunc) gin.HandlerFunc } // Providers available or no proxy for fallback, restore body and use normal handler + // Filter Anthropic-Beta header to remove features requiring special subscription + // This is needed when using local providers (bypassing the Amp proxy) + if betaHeader := c.Request.Header.Get("Anthropic-Beta"); betaHeader != "" { + filtered := filterBetaFeatures(betaHeader, "context-1m-2025-08-07") + if filtered != "" { + c.Request.Header.Set("Anthropic-Beta", filtered) + } else { + c.Request.Header.Del("Anthropic-Beta") + } + } + c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes)) handler(c) } diff --git a/internal/api/modules/amp/proxy.go b/internal/api/modules/amp/proxy.go index 5e267290..d417d068 100644 --- a/internal/api/modules/amp/proxy.go +++ b/internal/api/modules/amp/proxy.go @@ -46,6 +46,10 @@ func createReverseProxy(upstreamURL string, secretSource SecretSource) (*httputi // Could generate one here if needed } + // Note: We do NOT filter Anthropic-Beta headers in the proxy path + // Users going through ampcode.com proxy are paying for the service and should get all features + // including 1M context window (context-1m-2025-08-07) + // Inject API key from secret source (precedence: config > env > file) if key, err := secretSource.Get(req.Context()); err == nil && key != "" { req.Header.Set("X-Api-Key", key) @@ -174,3 +178,18 @@ func proxyHandler(proxy *httputil.ReverseProxy) gin.HandlerFunc { proxy.ServeHTTP(c.Writer, c.Request) } } + +// filterBetaFeatures removes a specific beta feature from comma-separated list +func filterBetaFeatures(header, featureToRemove string) string { + features := strings.Split(header, ",") + filtered := make([]string, 0, len(features)) + + for _, feature := range features { + trimmed := strings.TrimSpace(feature) + if trimmed != "" && trimmed != featureToRemove { + filtered = append(filtered, trimmed) + } + } + + return strings.Join(filtered, ",") +} diff --git a/internal/api/modules/amp/proxy_test.go b/internal/api/modules/amp/proxy_test.go index 864ed22c..a9694c01 100644 --- a/internal/api/modules/amp/proxy_test.go +++ b/internal/api/modules/amp/proxy_test.go @@ -437,3 +437,64 @@ func TestIsStreamingResponse(t *testing.T) { }) } } + +func TestFilterBetaFeatures(t *testing.T) { + tests := []struct { + name string + header string + featureToRemove string + expected string + }{ + { + name: "Remove context-1m from middle", + header: "fine-grained-tool-streaming-2025-05-14,context-1m-2025-08-07,oauth-2025-04-20", + featureToRemove: "context-1m-2025-08-07", + expected: "fine-grained-tool-streaming-2025-05-14,oauth-2025-04-20", + }, + { + name: "Remove context-1m from start", + header: "context-1m-2025-08-07,fine-grained-tool-streaming-2025-05-14", + featureToRemove: "context-1m-2025-08-07", + expected: "fine-grained-tool-streaming-2025-05-14", + }, + { + name: "Remove context-1m from end", + header: "fine-grained-tool-streaming-2025-05-14,context-1m-2025-08-07", + featureToRemove: "context-1m-2025-08-07", + expected: "fine-grained-tool-streaming-2025-05-14", + }, + { + name: "Feature not present", + header: "fine-grained-tool-streaming-2025-05-14,oauth-2025-04-20", + featureToRemove: "context-1m-2025-08-07", + expected: "fine-grained-tool-streaming-2025-05-14,oauth-2025-04-20", + }, + { + name: "Only feature to remove", + header: "context-1m-2025-08-07", + featureToRemove: "context-1m-2025-08-07", + expected: "", + }, + { + name: "Empty header", + header: "", + featureToRemove: "context-1m-2025-08-07", + expected: "", + }, + { + name: "Header with spaces", + header: "fine-grained-tool-streaming-2025-05-14, context-1m-2025-08-07 , oauth-2025-04-20", + featureToRemove: "context-1m-2025-08-07", + expected: "fine-grained-tool-streaming-2025-05-14,oauth-2025-04-20", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := filterBetaFeatures(tt.header, tt.featureToRemove) + if result != tt.expected { + t.Errorf("filterBetaFeatures() = %q, want %q", result, tt.expected) + } + }) + } +}