1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
use crate::config::AppConfig;
use stakpak_api::{AgentClient, AgentClientConfig, AgentProvider, StakpakConfig};
use tokio::time::Duration;
const MAX_RETRIES: u32 = 2;
/// Validate profile switch before committing to it
/// - Loads the new profile configuration
/// - Inherits API key from default if new profile doesn't have one
/// - Validates API key with retry logic
pub async fn validate_profile_switch(
new_profile: &str,
config_path: Option<&str>,
default_api_key: Option<String>,
) -> Result<AppConfig, String> {
// 1. Try to load the new profile config
let mut new_config = AppConfig::load(new_profile, config_path)
.map_err(|e| format!("Failed to load profile '{}': {}", new_profile, e))?;
// 2. Handle API key - inherit from default if not present and new profile is remote
// Only inherit the Stakpak API key for remote profiles; local profiles use their own
// provider credentials and inheriting a Stakpak key would cause model routing confusion
// (e.g., use_stakpak=true would transform model IDs for Stakpak proxy format).
if new_config.api_key.is_none()
&& matches!(new_config.provider, crate::config::ProviderType::Remote)
&& let Some(default_key) = default_api_key
{
new_config.api_key = Some(default_key);
}
// 3. Create AgentClient and test API connection with retry logic
let client: Box<dyn AgentProvider> = {
// Use credential resolution with auth.toml fallback chain
let stakpak = new_config
.get_stakpak_api_key()
.map(|api_key| StakpakConfig {
api_key,
api_endpoint: new_config.api_endpoint.clone(),
});
let client = AgentClient::new(AgentClientConfig {
stakpak,
providers: new_config.get_llm_provider_config(),
// Pass unified model as smart_model for AgentClient compatibility
smart_model: new_config.model.clone(),
eco_model: None,
recovery_model: None,
store_path: None,
hook_registry: None,
})
.await
.map_err(|e| format!("Failed to create agent client: {}", e))?;
Box::new(client)
};
let mut last_error = String::new();
for attempt in 1..=MAX_RETRIES {
match client.get_my_account().await {
Ok(_) => {
// Success!
return Ok(new_config);
}
Err(e) => {
// If no Stakpak key, the local stub account is returned, so this should succeed
// If it fails, it's a real error
last_error = e;
if attempt < MAX_RETRIES {
// Wait before retry (exponential backoff)
tokio::time::sleep(Duration::from_secs(attempt as u64)).await;
}
}
}
}
Err(format!(
"API validation failed after {} attempts: {}",
MAX_RETRIES, last_error
))
}