Skip to content

Commit 76c9560

Browse files
committed
feat: support ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, and SPACEBOT_MODEL env vars
Add environment variable support for custom Anthropic API endpoints and authentication tokens, enabling use with API proxies (LiteLLM, Azure AI Gateway, corporate proxies). New environment variables: - ANTHROPIC_BASE_URL: override the Anthropic API endpoint - ANTHROPIC_AUTH_TOKEN: alternative to ANTHROPIC_API_KEY for proxy auth - SPACEBOT_MODEL: override all process types with a single model Also fixes a bug where build_anthropic_request() hardcoded the API URL instead of using the provider's configured base_url, making custom base URLs via TOML config ineffective. Closes #132
1 parent 547d700 commit 76c9560

3 files changed

Lines changed: 41 additions & 8 deletions

File tree

src/config.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,7 +1803,9 @@ impl Config {
18031803
/// Load from environment variables only (no config file).
18041804
pub fn load_from_env(instance_dir: &Path) -> Result<Self> {
18051805
let mut llm = LlmConfig {
1806-
anthropic_key: std::env::var("ANTHROPIC_API_KEY").ok(),
1806+
anthropic_key: std::env::var("ANTHROPIC_API_KEY")
1807+
.ok()
1808+
.or_else(|| std::env::var("ANTHROPIC_AUTH_TOKEN").ok()),
18071809
openai_key: std::env::var("OPENAI_API_KEY").ok(),
18081810
openrouter_key: std::env::var("OPENROUTER_API_KEY").ok(),
18091811
zhipu_key: std::env::var("ZHIPU_API_KEY").ok(),
@@ -1826,11 +1828,13 @@ impl Config {
18261828

18271829
// Populate providers from env vars (same as from_toml does)
18281830
if let Some(anthropic_key) = llm.anthropic_key.clone() {
1831+
let base_url = std::env::var("ANTHROPIC_BASE_URL")
1832+
.unwrap_or_else(|_| ANTHROPIC_PROVIDER_BASE_URL.to_string());
18291833
llm.providers
18301834
.entry("anthropic".to_string())
18311835
.or_insert_with(|| ProviderConfig {
18321836
api_type: ApiType::Anthropic,
1833-
base_url: ANTHROPIC_PROVIDER_BASE_URL.to_string(),
1837+
base_url,
18341838
api_key: anthropic_key,
18351839
name: None,
18361840
});
@@ -1938,8 +1942,16 @@ impl Config {
19381942
// Note: We allow boot without provider keys now. System starts in setup mode.
19391943
// Agents are initialized later when keys are added via API.
19401944

1941-
// Env-only routing: check for env overrides on channel/worker models
1945+
// Env-only routing: check for env overrides on channel/worker models.
1946+
// SPACEBOT_MODEL overrides all process types at once; specific vars take precedence.
19421947
let mut routing = RoutingConfig::default();
1948+
if let Ok(model) = std::env::var("SPACEBOT_MODEL") {
1949+
routing.channel = model.clone();
1950+
routing.branch = model.clone();
1951+
routing.worker = model.clone();
1952+
routing.compactor = model.clone();
1953+
routing.cortex = model;
1954+
}
19431955
if let Ok(channel_model) = std::env::var("SPACEBOT_CHANNEL_MODEL") {
19441956
routing.channel = channel_model;
19451957
}
@@ -2039,7 +2051,8 @@ impl Config {
20392051
.anthropic_key
20402052
.as_deref()
20412053
.and_then(resolve_env_value)
2042-
.or_else(|| std::env::var("ANTHROPIC_API_KEY").ok()),
2054+
.or_else(|| std::env::var("ANTHROPIC_API_KEY").ok())
2055+
.or_else(|| std::env::var("ANTHROPIC_AUTH_TOKEN").ok()),
20432056
openai_key: toml
20442057
.llm
20452058
.openai_key
@@ -2162,11 +2175,13 @@ impl Config {
21622175
};
21632176

21642177
if let Some(anthropic_key) = llm.anthropic_key.clone() {
2178+
let base_url = std::env::var("ANTHROPIC_BASE_URL")
2179+
.unwrap_or_else(|_| ANTHROPIC_PROVIDER_BASE_URL.to_string());
21652180
llm.providers
21662181
.entry("anthropic".to_string())
21672182
.or_insert_with(|| ProviderConfig {
21682183
api_type: ApiType::Anthropic,
2169-
base_url: ANTHROPIC_PROVIDER_BASE_URL.to_string(),
2184+
base_url,
21702185
api_key: anthropic_key,
21712186
name: None,
21722187
});

src/llm/anthropic/params.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use super::tools;
77
use reqwest::RequestBuilder;
88
use rig::completion::CompletionRequest;
99

10-
const ANTHROPIC_API_URL: &str = "https://api.anthropic.com/v1/messages";
1110
const CLAUDE_CODE_SYSTEM_PREAMBLE: &str =
1211
"You are Claude Code, Anthropic's official CLI for Claude.";
1312

@@ -32,21 +31,39 @@ fn is_opus(model_id: &str) -> bool {
3231
model_id.contains("opus")
3332
}
3433

34+
/// Construct the full messages endpoint URL from a base URL.
35+
///
36+
/// If the base URL already ends with a path segment (e.g. `/v1/messages`),
37+
/// use it as-is. Otherwise append `/v1/messages`.
38+
fn messages_url(base_url: &str) -> String {
39+
let trimmed = base_url.trim_end_matches('/');
40+
if trimmed.ends_with("/v1/messages") {
41+
trimmed.to_string()
42+
} else {
43+
format!("{trimmed}/v1/messages")
44+
}
45+
}
46+
3547
/// Build a fully configured Anthropic API request from a CompletionRequest.
3648
///
49+
/// `base_url` is the provider's configured base URL (e.g. `https://api.anthropic.com`
50+
/// or a custom proxy). The `/v1/messages` path is appended automatically.
51+
///
3752
/// `thinking_effort` controls adaptive thinking: "auto" picks max for Opus /
3853
/// high for others, or pass "max", "high", "medium", "low" explicitly.
3954
pub fn build_anthropic_request(
4055
http_client: &reqwest::Client,
4156
api_key: &str,
57+
base_url: &str,
4258
model_name: &str,
4359
request: &CompletionRequest,
4460
thinking_effort: &str,
4561
) -> AnthropicRequest {
4662
let is_oauth = auth::detect_auth_path(api_key) == AnthropicAuthPath::OAuthToken;
4763
let adaptive_thinking = supports_adaptive_thinking(model_name);
4864
let retention = cache::resolve_cache_retention(None);
49-
let cache_control = cache::get_cache_control(ANTHROPIC_API_URL, retention);
65+
let url = messages_url(base_url);
66+
let cache_control = cache::get_cache_control(&url, retention);
5067

5168
let mut body = serde_json::json!({
5269
"model": model_name,
@@ -80,7 +97,7 @@ pub fn build_anthropic_request(
8097
}
8198

8299
let builder = http_client
83-
.post(ANTHROPIC_API_URL)
100+
.post(&url)
84101
.header("anthropic-version", "2023-06-01")
85102
.header("content-type", "application/json");
86103

src/llm/model.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ impl SpacebotModel {
352352
let anthropic_request = crate::llm::anthropic::build_anthropic_request(
353353
self.llm_manager.http_client(),
354354
api_key,
355+
&provider_config.base_url,
355356
&self.model_name,
356357
&request,
357358
effort,

0 commit comments

Comments
 (0)