Merge pull request #92 from 0xMassi/feat/atlascloud-provider

feat(llm): add Atlas Cloud as an opt-in provider
This commit is contained in:
Valerio 2026-07-22 17:44:13 +02:00 committed by GitHub
commit 63056a8968
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 88 additions and 2 deletions

View file

@ -338,7 +338,7 @@ struct Cli {
#[arg(long, num_args = 0..=1, default_missing_value = "3")] #[arg(long, num_args = 0..=1, default_missing_value = "3")]
summarize: Option<usize>, summarize: Option<usize>,
/// Force a specific LLM provider (ollama, openai, anthropic) /// Force a specific LLM provider (ollama, openai, atlascloud, anthropic)
#[arg(long, env = "WEBCLAW_LLM_PROVIDER")] #[arg(long, env = "WEBCLAW_LLM_PROVIDER")]
llm_provider: Option<String>, llm_provider: Option<String>,
@ -2239,6 +2239,15 @@ async fn build_llm_provider(cli: &Cli) -> Result<Box<dyn LlmProvider>, String> {
.ok_or("OPENAI_API_KEY not set")?; .ok_or("OPENAI_API_KEY not set")?;
Ok(Box::new(provider)) Ok(Box::new(provider))
} }
"atlascloud" => {
let provider = webclaw_llm::providers::atlascloud::AtlasCloudProvider::new(
None,
cli.llm_base_url.clone(),
cli.llm_model.clone(),
)
.ok_or("ATLASCLOUD_API_KEY not set")?;
Ok(Box::new(provider))
}
"anthropic" => { "anthropic" => {
let provider = webclaw_llm::providers::anthropic::AnthropicProvider::with_base_url( let provider = webclaw_llm::providers::anthropic::AnthropicProvider::with_base_url(
None, None,
@ -2249,7 +2258,7 @@ async fn build_llm_provider(cli: &Cli) -> Result<Box<dyn LlmProvider>, String> {
Ok(Box::new(provider)) Ok(Box::new(provider))
} }
other => Err(format!( other => Err(format!(
"unknown LLM provider: {other} (use ollama, openai, or anthropic)" "unknown LLM provider: {other} (use ollama, openai, atlascloud, or anthropic)"
)), )),
} }
} else { } else {

View file

@ -0,0 +1,76 @@
/// Atlas Cloud provider — OpenAI-compatible chat completions with Atlas defaults.
use async_trait::async_trait;
use crate::error::LlmError;
use crate::provider::{CompletionRequest, LlmProvider};
use super::openai::OpenAiProvider;
pub struct AtlasCloudProvider {
inner: OpenAiProvider,
}
impl AtlasCloudProvider {
/// Returns `None` if no Atlas Cloud API key is available (param or env).
pub fn new(
key_override: Option<String>,
base_url: Option<String>,
model: Option<String>,
) -> Option<Self> {
let key = super::load_api_key(key_override, "ATLASCLOUD_API_KEY")?;
let base_url = base_url
.or_else(|| std::env::var("ATLASCLOUD_BASE_URL").ok())
.unwrap_or_else(|| "https://api.atlascloud.ai/v1".into());
let model = model.unwrap_or_else(|| "qwen/qwen3.5-flash".into());
let inner = OpenAiProvider::new(Some(key), Some(base_url), Some(model))?;
Some(Self { inner })
}
pub fn default_model(&self) -> &str {
self.inner.default_model()
}
}
#[async_trait]
impl LlmProvider for AtlasCloudProvider {
async fn complete(&self, request: &CompletionRequest) -> Result<String, LlmError> {
self.inner.complete(request).await
}
async fn is_available(&self) -> bool {
self.inner.is_available().await
}
fn name(&self) -> &str {
"atlascloud"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_key_returns_none() {
assert!(AtlasCloudProvider::new(Some(String::new()), None, None).is_none());
}
#[test]
fn explicit_key_constructs_with_atlas_defaults() {
let provider =
AtlasCloudProvider::new(Some("test-key".into()), None, None).expect("should construct");
assert_eq!(provider.name(), "atlascloud");
assert_eq!(provider.default_model(), "qwen/qwen3.5-flash");
}
#[test]
fn explicit_model_override() {
let provider = AtlasCloudProvider::new(
Some("test-key".into()),
Some("https://proxy.example.com/v1".into()),
Some("deepseek-ai/deepseek-v4-pro".into()),
)
.expect("should construct");
assert_eq!(provider.default_model(), "deepseek-ai/deepseek-v4-pro");
}
}

View file

@ -1,4 +1,5 @@
pub mod anthropic; pub mod anthropic;
pub mod atlascloud;
pub mod gemini; pub mod gemini;
pub mod ollama; pub mod ollama;
pub mod openai; pub mod openai;