1use crate::error::{AppError, Result};
2
3#[derive(Debug, Clone)]
4pub struct Config {
5 pub model: ModelConfig,
6 pub anthropic: AnthropicConfig,
7}
8
9#[derive(Debug, Clone)]
10pub struct ModelConfig {
11 pub provider: String,
12 pub name: String,
13 pub max_tokens: u32,
14}
15
16#[derive(Debug, Clone)]
17pub struct AnthropicConfig {
18 pub api_key: Option<String>,
19 pub base_url: String,
20}
21
22impl Default for Config {
23 fn default() -> Self {
24 Self {
25 model: ModelConfig {
26 provider: "anthropic".to_string(),
27 name: "claude-sonnet-4-6".to_string(),
28 max_tokens: 8192,
29 },
30 anthropic: AnthropicConfig {
31 api_key: None,
32 base_url: "https://api.anthropic.com".to_string(),
33 },
34 }
35 }
36}
37
38impl Config {
39 pub fn from_env() -> Result<Self> {
40 Self::from_env_with(|name| std::env::var(name).ok())
41 }
42
43 pub fn from_env_with<F>(lookup: F) -> Result<Self>
44 where
45 F: Fn(&str) -> Option<String>,
46 {
47 let mut cfg = Self::default();
48 if let Some(name) = lookup("CAPO_MODEL_NAME") {
49 cfg.model.name = name.trim().to_string();
50 }
51 if let Some(provider) = lookup("CAPO_MODEL_PROVIDER") {
52 cfg.model.provider = provider.trim().to_string();
53 }
54 if let Some(max_tokens) = lookup("CAPO_MODEL_MAX_TOKENS") {
55 let max_tokens = max_tokens.trim().to_string();
56 cfg.model.max_tokens = max_tokens.parse().map_err(|_| {
57 AppError::Config(format!(
58 "CAPO_MODEL_MAX_TOKENS not an integer: {max_tokens}"
59 ))
60 })?;
61 }
62 if let Some(key) = lookup("ANTHROPIC_API_KEY") {
63 cfg.anthropic.api_key = Some(key.trim().to_string());
64 }
65 if let Some(url) = lookup("CAPO_ANTHROPIC_BASE_URL") {
66 cfg.anthropic.base_url = url.trim().to_string();
67 }
68 Ok(cfg)
69 }
70}