Append Anthropic path prefix when writing ANTHROPIC_BASE_URL#5160
Closed
Append Anthropic path prefix when writing ANTHROPIC_BASE_URL#5160
Conversation
Envoy AI Gateway routes native-Anthropic traffic at /anthropic/v1/messages, not /v1/messages. Without the prefix, Claude Code's requests fall through to the catchall route and the user sees "model may not exist or you may not have access" while gateway logs show 404s. Add a persisted llm.anthropic_path_prefix setting and an --anthropic-path-prefix flag on both "thv llm setup" and "thv llm config set". When non-empty, the prefix is appended to the gateway URL before ANTHROPIC_BASE_URL is written into Claude Code's settings file. The proxy-mode upstream is unaffected because proxy-mode tools forward OpenAI-shaped traffic on a separate route. Closes #5158 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5160 +/- ##
==========================================
- Coverage 67.56% 67.53% -0.04%
==========================================
Files 601 601
Lines 61093 61130 +37
==========================================
+ Hits 41280 41283 +3
- Misses 16697 16730 +33
- Partials 3116 3117 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Most ToolHive deployments target Envoy AI Gateway, which routes native-Anthropic traffic at /anthropic. Make that the default so users do not have to know to pass --anthropic-path-prefix=/anthropic. To distinguish "not set, use default" from "explicitly empty" (LiteLLM or direct Anthropic), Config.AnthropicPathPrefix becomes a *string and SetOptions.AnthropicPathPrefix follows the same TLSSkipVerify pointer pattern. Add EffectiveAnthropicPathPrefix() to resolve nil → default. The CLI flags use cmd.Flags().Changed() so --anthropic-path-prefix="" correctly persists an empty override instead of being discarded as a zero value. Show now reports the effective value with (default) or (none — direct Anthropic / LiteLLM) annotations so users can tell at a glance which mode they are in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Copy *string value in SetFields rather than aliasing the pointer, per the copy-before-mutating-caller-input rule - Note in the AnthropicPathPrefix field comment that omitempty on a *string applies only to nil — a non-nil pointer to "" is persisted, which is required for the "explicit no prefix" opt-out to survive restarts - Refactor Show to compute the display string via an IIFE before passing it to writef, matching the immutable-assignment pattern - Expand the ValueField enum comment in LLMGatewayKeySpec to include "AnthropicBaseURL"; move it to a pre-field block comment to avoid the continuation line dangling between two struct fields - Fix llmValueForSpec comment style to match the adjacent "For ..." form Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Closing in favor of https://github.com/stacklok/toolhive/pull/5174/changes |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/anthropic/v1/messages, butthv llm setupwritesANTHROPIC_BASE_URL=<gateway-url>verbatim. Claude Code's requests fall through to Envoy's catchall and the user sees "model may not exist or you may not have access" while gateway logs show 404s with auth headers populated — auth works, the path is wrong.llm.anthropic_path_prefixsetting (typed as a nullable string soniland""are distinguishable) and an--anthropic-path-prefixflag on boththv llm setupandthv llm config set./anthropic(Envoy AI Gateway is the primary target). Users on LiteLLM or direct Anthropic opt out with--anthropic-path-prefix="". The CLI usescmd.Flags().Changed()so an explicit empty override is persisted instead of being discarded as a zero value, mirroring the existing--tls-skip-verifypattern.thv llm proxyon a separate route.Closes #5158
Type of change
Test plan
task test)task lint-fix)Manual: ran
go run ./cmd/thv llm setup --helpandgo run ./cmd/thv llm config set --helpto confirm the flag advertises the/anthropicdefault and the opt-out instruction on both commands.New unit tests cover (red→green TDD):
pkg/client/llm_gateway_test.go::TestConfigureLLMGateway_AnthropicPathPrefix— emptyApplyConfig.AnthropicPathPrefixproduces a bareANTHROPIC_BASE_URL(regression guard);/anthropicproduces<gateway>/anthropic.pkg/llm/config_test.go::TestConfig_EffectiveAnthropicPathPrefix— nil resolves to/anthropic(default), explicit empty stays empty, explicit value passes through.pkg/llm/manage_test.go::TestConfig_SetFields— pointer cases assert that&"/anthropic"is accepted,&""opts out, and bare values / query strings / shell metacharacters are rejected.API Compatibility
v1beta1API, OR theapi-break-allowedlabel is applied and the migration guidance is described above.Does this introduce a user-facing change?
Yes. After upgrade, a fresh
thv llm setupwritesANTHROPIC_BASE_URL=<gateway>/anthropicby default — the right value for Envoy AI Gateway, which is the primary deployment target. Users on LiteLLM or direct Anthropic must now pass--anthropic-path-prefix=""(or setllm.anthropic_path_prefix: ""in~/.toolhive/config.yaml).thv llm config showreports the resolved value with a(default)or(none — direct Anthropic / LiteLLM)annotation so the active mode is visible at a glance.Implementation plan
Approved implementation plan
Approach
Persist the prefix in LLM config (it's a property of the deployed gateway, not a per-tool setting), expose it as a flag on
thv llm setupandthv llm config set, and append it only when writingANTHROPIC_BASE_URLfor direct-mode tools. Default to/anthropicto optimise for the most common deployment (Envoy AI Gateway). The optional auto-probe from the issue is intentionally skipped — the issue marks it optional, and an HTTP probe over an OIDC-protected endpoint adds meaningful complexity (timeouts, TLS skip handling, false negatives) for marginal benefit when the flag is explicit.Changes
pkg/llm/config.go— add nullableAnthropicPathPrefix *stringtoConfig,DefaultAnthropicPathPrefix = "/anthropic",EffectiveAnthropicPathPrefix(); validate the prefix inValidatePartial(must start with/, no query/fragment, length cap, no shell metacharacters).pkg/llm/manage.go—SetOptions.AnthropicPathPrefix *string;Showannotates(default)and(none)modes.pkg/llmgateway/config.go— addAnthropicPathPrefix stringtoApplyConfig(resolved value).pkg/llm/setup.go— pipeEffectiveAnthropicPathPrefix()intoconfigureDetectedTools.pkg/client/llm_gateway.go— newAnthropicBaseURLValueField returnscfg.GatewayURL + cfg.AnthropicPathPrefix.pkg/client/config.go— Claude Code's/env/ANTHROPIC_BASE_URLspec uses the new ValueField.cmd/thv/app/llm.go— register--anthropic-path-prefixon bothsetupandconfig set; flag default is/anthropic; useFlags().Changed()to persist explicit empty overrides.Out of scope (intentional)
thv llm proxyand don't need the prefix in tool config; the proxy forwards OpenAI-shaped traffic on a separate Envoy route.Special notes for reviewers
/anthropicand switches the field to*stringso the empty-override case can be persisted. Both commits keep tests green.Hidden: truein the CLI tree, sotask docsdoes not need to regenerate anything for this PR.Generated with Claude Code