fix: safely append beta query for anthropic target URLs #672
fix: safely append beta query for anthropic target URLs #672gopkg-dev wants to merge 3 commits intoWei-Shaw:mainfrom
Conversation
Co-authored-by: gopkg-dev <58848833+gopkg-dev@users.noreply.github.com>
Co-authored-by: gopkg-dev <58848833+gopkg-dev@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the gateway’s Anthropic upstream URL construction so beta=true is consistently present when targeting custom Anthropic base URLs, and adjusts passthrough tests to assert the new URL shape.
Changes:
- Added
buildAnthropicTargetURLhelper to append/setbeta=truevia URL parsing. - Updated Anthropic upstream request builders to use the new helper for
/v1/messagesand/v1/messages/count_tokens. - Updated API-key passthrough tests to assert
beta=trueappears in the upstream request URI.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| backend/internal/service/gateway_service.go | Introduces centralized Anthropic target URL builder and switches request builders to use it. |
| backend/internal/service/gateway_anthropic_apikey_passthrough_test.go | Adds assertions that upstream requests include beta=true in the URI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| targetURL := validatedURL + endpoint | ||
| parsedURL, err := url.Parse(targetURL) | ||
| if err != nil { | ||
| if strings.Contains(targetURL, "?") { | ||
| return targetURL + "&beta=true" | ||
| } | ||
| return targetURL + "?beta=true" | ||
| } | ||
| query := parsedURL.Query() | ||
| query.Set("beta", "true") | ||
| parsedURL.RawQuery = query.Encode() | ||
| return parsedURL.String() |
There was a problem hiding this comment.
buildAnthropicTargetURL builds targetURL via plain string concatenation (validatedURL + endpoint). If validatedURL already contains a query string (e.g. https://proxy.example.com/api?token=abc), the resulting URL becomes .../api?token=abc/v1/messages, which parses as query token=abc/v1/messages and loses the intended path. This means the new helper still breaks the exact “base URL already has query parameters” case the PR is trying to handle.
Suggestion: parse validatedURL first, parse endpoint separately, then join paths (preserving any base path prefix) and merge query params before setting beta=true. Consider returning (string, error) instead of silently appending on parse errors, since validatedURL is expected to be valid after validateUpstreamBaseURL.
| targetURL := validatedURL + endpoint | |
| parsedURL, err := url.Parse(targetURL) | |
| if err != nil { | |
| if strings.Contains(targetURL, "?") { | |
| return targetURL + "&beta=true" | |
| } | |
| return targetURL + "?beta=true" | |
| } | |
| query := parsedURL.Query() | |
| query.Set("beta", "true") | |
| parsedURL.RawQuery = query.Encode() | |
| return parsedURL.String() | |
| // Parse the validated base URL first. | |
| baseURL, err := url.Parse(validatedURL) | |
| if err != nil { | |
| // Fallback to previous behavior if, unexpectedly, the base URL is invalid. | |
| targetURL := validatedURL + endpoint | |
| if strings.Contains(targetURL, "?") { | |
| return targetURL + "&beta=true" | |
| } | |
| return targetURL + "?beta=true" | |
| } | |
| // Parse the endpoint separately to extract any path and query components. | |
| endpointURL, err := url.Parse(endpoint) | |
| if err != nil { | |
| // If endpoint cannot be parsed, fallback to concatenation behavior. | |
| targetURL := validatedURL + endpoint | |
| if strings.Contains(targetURL, "?") { | |
| return targetURL + "&beta=true" | |
| } | |
| return targetURL + "?beta=true" | |
| } | |
| // Join paths while preserving any base path prefix (e.g. "/api" + "/v1/messages"). | |
| basePath := strings.TrimRight(baseURL.Path, "/") | |
| endpointPath := strings.TrimLeft(endpointURL.Path, "/") | |
| if basePath == "" { | |
| baseURL.Path = "/" + endpointPath | |
| } else if endpointPath == "" { | |
| baseURL.Path = basePath | |
| } else { | |
| baseURL.Path = basePath + "/" + endpointPath | |
| } | |
| // Merge query parameters from base and endpoint, then set beta=true. | |
| query := baseURL.Query() | |
| endpointQuery := endpointURL.Query() | |
| for key, values := range endpointQuery { | |
| // Overwrite base values with endpoint values for the same key. | |
| query[key] = values | |
| } | |
| query.Set("beta", "true") | |
| baseURL.RawQuery = query.Encode() | |
| return baseURL.String() |
| require.Equal(t, "claude-3-7-sonnet-20250219", gjson.GetBytes(upstream.lastBody, "model").String()) | ||
|
|
||
| require.Equal(t, "upstream-anthropic-key", upstream.lastReq.Header.Get("x-api-key")) | ||
| require.Equal(t, "/v1/messages?beta=true", upstream.lastReq.URL.RequestURI()) |
There was a problem hiding this comment.
These assertions only cover the no-existing-query case (/v1/messages?beta=true). Given the goal is to preserve/append beta=true when the configured base_url already has query parameters, add a test case where base_url includes something like a path prefix and/or query (e.g. https://proxy.example.com/api?token=abc) and assert the resulting upstream RequestURI keeps the original query and path while adding beta=true (order-agnostic).
| require.Equal(t, body, upstream.lastBody, "count_tokens 透传模式不应改写请求体") | ||
| require.Equal(t, "claude-3-5-sonnet-latest", gjson.GetBytes(upstream.lastBody, "model").String()) | ||
| require.Equal(t, "upstream-anthropic-key", upstream.lastReq.Header.Get("x-api-key")) | ||
| require.Equal(t, "/v1/messages/count_tokens?beta=true", upstream.lastReq.URL.RequestURI()) |
There was a problem hiding this comment.
This assertion only checks the simple ?beta=true append scenario. To validate the intended behavior, add coverage for base_url values that already include query params (and/or a path prefix), and assert the produced RequestURI preserves existing query keys while adding beta=true (prefer parsing/query comparison rather than exact string order).
This pull request updates the way our gateway service constructs upstream Anthropic API URLs to ensure the
beta=truequery parameter is always present, regardless of the endpoint or existing query parameters. This change centralizes and standardizes URL construction, reducing the risk of missing or malformed beta flags. Relevant tests have also been updated to verify the new URL structure.URL construction improvements:
buildAnthropicTargetURLingateway_service.goto append or set thebeta=truequery parameter correctly for all Anthropic API requests, handling cases where the endpoint already has query parameters.buildUpstreamRequestAnthropicAPIKeyPassthrough,buildUpstreamRequest,buildCountTokensRequestAnthropicAPIKeyPassthrough,buildCountTokensRequest) to use the new helper for constructing URLs. [1] [2] [3] [4]net/urlto support the new URL construction logic.Test updates:
beta=truequery parameter. [1] [2]