Skip to content

fix: safely append beta query for anthropic target URLs #672

Open
gopkg-dev wants to merge 3 commits intoWei-Shaw:mainfrom
gopkg-dev:copilot/fix-target-url-parameter
Open

fix: safely append beta query for anthropic target URLs #672
gopkg-dev wants to merge 3 commits intoWei-Shaw:mainfrom
gopkg-dev:copilot/fix-target-url-parameter

Conversation

@gopkg-dev
Copy link

This pull request updates the way our gateway service constructs upstream Anthropic API URLs to ensure the beta=true query 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:

  • Added a new helper function buildAnthropicTargetURL in gateway_service.go to append or set the beta=true query parameter correctly for all Anthropic API requests, handling cases where the endpoint already has query parameters.
  • Updated all Anthropic upstream request builders (buildUpstreamRequestAnthropicAPIKeyPassthrough, buildUpstreamRequest, buildCountTokensRequestAnthropicAPIKeyPassthrough, buildCountTokensRequest) to use the new helper for constructing URLs. [1] [2] [3] [4]
  • Added the necessary import for net/url to support the new URL construction logic.

Test updates:

  • Enhanced tests to assert that requests to Anthropic endpoints now include the correct beta=true query parameter. [1] [2]
Copilot AI and others added 3 commits February 27, 2026 17:46
Co-authored-by: gopkg-dev <58848833+gopkg-dev@users.noreply.github.com>
Co-authored-by: gopkg-dev <58848833+gopkg-dev@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 27, 2026 18:03
@gopkg-dev gopkg-dev changed the title Preserve and safely append beta query for custom Anthropics URLs Feb 27, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 buildAnthropicTargetURL helper to append/set beta=true via URL parsing.
  • Updated Anthropic upstream request builders to use the new helper for /v1/messages and /v1/messages/count_tokens.
  • Updated API-key passthrough tests to assert beta=true appears 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.

Comment on lines +57 to +68
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()
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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()
Copilot uses AI. Check for mistakes.
Comment on lines 175 to +178
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())
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines 257 to +260
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())
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants