Skip to content

Commit fa635b3

Browse files
authored
Merge pull request #135 from adryserage/feat/custom-anthropic-env-vars
feat: support ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN and SPACEBOT_MODEL env vars
2 parents a220bbc + 76c9560 commit fa635b3

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
@@ -1923,7 +1923,9 @@ impl Config {
19231923
/// Load from environment variables only (no config file).
19241924
pub fn load_from_env(instance_dir: &Path) -> Result<Self> {
19251925
let mut llm = LlmConfig {
1926-
anthropic_key: std::env::var("ANTHROPIC_API_KEY").ok(),
1926+
anthropic_key: std::env::var("ANTHROPIC_API_KEY")
1927+
.ok()
1928+
.or_else(|| std::env::var("ANTHROPIC_AUTH_TOKEN").ok()),
19271929
openai_key: std::env::var("OPENAI_API_KEY").ok(),
19281930
openrouter_key: std::env::var("OPENROUTER_API_KEY").ok(),
19291931
zhipu_key: std::env::var("ZHIPU_API_KEY").ok(),
@@ -1947,11 +1949,13 @@ impl Config {
19471949

19481950
// Populate providers from env vars (same as from_toml does)
19491951
if let Some(anthropic_key) = llm.anthropic_key.clone() {
1952+
let base_url = std::env::var("ANTHROPIC_BASE_URL")
1953+
.unwrap_or_else(|_| ANTHROPIC_PROVIDER_BASE_URL.to_string());
19501954
llm.providers
19511955
.entry("anthropic".to_string())
19521956
.or_insert_with(|| ProviderConfig {
19531957
api_type: ApiType::Anthropic,
1954-
base_url: ANTHROPIC_PROVIDER_BASE_URL.to_string(),
1958+
base_url,
19551959
api_key: anthropic_key,
19561960
name: None,
19571961
});
@@ -2081,8 +2085,16 @@ impl Config {
20812085
// Note: We allow boot without provider keys now. System starts in setup mode.
20822086
// Agents are initialized later when keys are added via API.
20832087

2084-
// Env-only routing: check for env overrides on channel/worker models
2088+
// Env-only routing: check for env overrides on channel/worker models.
2089+
// SPACEBOT_MODEL overrides all process types at once; specific vars take precedence.
20852090
let mut routing = RoutingConfig::default();
2091+
if let Ok(model) = std::env::var("SPACEBOT_MODEL") {
2092+
routing.channel = model.clone();
2093+
routing.branch = model.clone();
2094+
routing.worker = model.clone();
2095+
routing.compactor = model.clone();
2096+
routing.cortex = model;
2097+
}
20862098
if let Ok(channel_model) = std::env::var("SPACEBOT_CHANNEL_MODEL") {
20872099
routing.channel = channel_model;
20882100
}
@@ -2182,7 +2194,8 @@ impl Config {
21822194
.anthropic_key
21832195
.as_deref()
21842196
.and_then(resolve_env_value)
2185-
.or_else(|| std::env::var("ANTHROPIC_API_KEY").ok()),
2197+
.or_else(|| std::env::var("ANTHROPIC_API_KEY").ok())
2198+
.or_else(|| std::env::var("ANTHROPIC_AUTH_TOKEN").ok()),
21862199
openai_key: toml
21872200
.llm
21882201
.openai_key
@@ -2316,11 +2329,13 @@ impl Config {
23162329
};
23172330

23182331
if let Some(anthropic_key) = llm.anthropic_key.clone() {
2332+
let base_url = std::env::var("ANTHROPIC_BASE_URL")
2333+
.unwrap_or_else(|_| ANTHROPIC_PROVIDER_BASE_URL.to_string());
23192334
llm.providers
23202335
.entry("anthropic".to_string())
23212336
.or_insert_with(|| ProviderConfig {
23222337
api_type: ApiType::Anthropic,
2323-
base_url: ANTHROPIC_PROVIDER_BASE_URL.to_string(),
2338+
base_url,
23242339
api_key: anthropic_key,
23252340
name: None,
23262341
});

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
@@ -420,6 +420,7 @@ impl SpacebotModel {
420420
let anthropic_request = crate::llm::anthropic::build_anthropic_request(
421421
self.llm_manager.http_client(),
422422
api_key,
423+
&provider_config.base_url,
423424
&self.model_name,
424425
&request,
425426
effort,

0 commit comments

Comments
 (0)