Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1803,7 +1803,9 @@ impl Config {
/// Load from environment variables only (no config file).
pub fn load_from_env(instance_dir: &Path) -> Result<Self> {
let mut llm = LlmConfig {
anthropic_key: std::env::var("ANTHROPIC_API_KEY").ok(),
anthropic_key: std::env::var("ANTHROPIC_API_KEY")
.ok()
.or_else(|| std::env::var("ANTHROPIC_AUTH_TOKEN").ok()),
openai_key: std::env::var("OPENAI_API_KEY").ok(),
openrouter_key: std::env::var("OPENROUTER_API_KEY").ok(),
zhipu_key: std::env::var("ZHIPU_API_KEY").ok(),
Expand All @@ -1826,11 +1828,13 @@ impl Config {

// Populate providers from env vars (same as from_toml does)
if let Some(anthropic_key) = llm.anthropic_key.clone() {
let base_url = std::env::var("ANTHROPIC_BASE_URL")
.unwrap_or_else(|_| ANTHROPIC_PROVIDER_BASE_URL.to_string());
llm.providers
.entry("anthropic".to_string())
.or_insert_with(|| ProviderConfig {
api_type: ApiType::Anthropic,
base_url: ANTHROPIC_PROVIDER_BASE_URL.to_string(),
base_url,
api_key: anthropic_key,
name: None,
});
Expand Down Expand Up @@ -1938,8 +1942,16 @@ impl Config {
// Note: We allow boot without provider keys now. System starts in setup mode.
// Agents are initialized later when keys are added via API.

// Env-only routing: check for env overrides on channel/worker models
// Env-only routing: check for env overrides on channel/worker models.
// SPACEBOT_MODEL overrides all process types at once; specific vars take precedence.
let mut routing = RoutingConfig::default();
if let Ok(model) = std::env::var("SPACEBOT_MODEL") {
routing.channel = model.clone();
routing.branch = model.clone();
routing.worker = model.clone();
routing.compactor = model.clone();
routing.cortex = model;
}
if let Ok(channel_model) = std::env::var("SPACEBOT_CHANNEL_MODEL") {
routing.channel = channel_model;
}
Expand Down Expand Up @@ -2039,7 +2051,8 @@ impl Config {
.anthropic_key
.as_deref()
.and_then(resolve_env_value)
.or_else(|| std::env::var("ANTHROPIC_API_KEY").ok()),
.or_else(|| std::env::var("ANTHROPIC_API_KEY").ok())
.or_else(|| std::env::var("ANTHROPIC_AUTH_TOKEN").ok()),
openai_key: toml
.llm
.openai_key
Expand Down Expand Up @@ -2162,11 +2175,13 @@ impl Config {
};

if let Some(anthropic_key) = llm.anthropic_key.clone() {
let base_url = std::env::var("ANTHROPIC_BASE_URL")
.unwrap_or_else(|_| ANTHROPIC_PROVIDER_BASE_URL.to_string());
llm.providers
.entry("anthropic".to_string())
.or_insert_with(|| ProviderConfig {
api_type: ApiType::Anthropic,
base_url: ANTHROPIC_PROVIDER_BASE_URL.to_string(),
base_url,
api_key: anthropic_key,
name: None,
});
Expand Down
23 changes: 20 additions & 3 deletions src/llm/anthropic/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use super::tools;
use reqwest::RequestBuilder;
use rig::completion::CompletionRequest;

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

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

/// Construct the full messages endpoint URL from a base URL.
///
/// If the base URL already ends with a path segment (e.g. `/v1/messages`),
/// use it as-is. Otherwise append `/v1/messages`.
fn messages_url(base_url: &str) -> String {
let trimmed = base_url.trim_end_matches('/');
if trimmed.ends_with("/v1/messages") {
trimmed.to_string()
} else {
format!("{trimmed}/v1/messages")
}
}

/// Build a fully configured Anthropic API request from a CompletionRequest.
///
/// `base_url` is the provider's configured base URL (e.g. `https://api.anthropic.com`
/// or a custom proxy). The `/v1/messages` path is appended automatically.
///
/// `thinking_effort` controls adaptive thinking: "auto" picks max for Opus /
/// high for others, or pass "max", "high", "medium", "low" explicitly.
pub fn build_anthropic_request(
http_client: &reqwest::Client,
api_key: &str,
base_url: &str,
model_name: &str,
request: &CompletionRequest,
thinking_effort: &str,
) -> AnthropicRequest {
let is_oauth = auth::detect_auth_path(api_key) == AnthropicAuthPath::OAuthToken;
let adaptive_thinking = supports_adaptive_thinking(model_name);
let retention = cache::resolve_cache_retention(None);
let cache_control = cache::get_cache_control(ANTHROPIC_API_URL, retention);
let url = messages_url(base_url);
let cache_control = cache::get_cache_control(&url, retention);

let mut body = serde_json::json!({
"model": model_name,
Expand Down Expand Up @@ -80,7 +97,7 @@ pub fn build_anthropic_request(
}

let builder = http_client
.post(ANTHROPIC_API_URL)
.post(&url)
.header("anthropic-version", "2023-06-01")
.header("content-type", "application/json");

Expand Down
1 change: 1 addition & 0 deletions src/llm/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ impl SpacebotModel {
let anthropic_request = crate::llm::anthropic::build_anthropic_request(
self.llm_manager.http_client(),
api_key,
&provider_config.base_url,
&self.model_name,
&request,
effort,
Expand Down