use std::sync::Arc;
use async_trait::async_trait;
use crate::models::{ModelFunction, ModelMetadata, ModelVariant};
use crate::providers::{
ApiEndpoint, HealthStatus, ModelFormat, Provider, ProviderError, PullResult,
};
use crate::registry::Secret;
use crate::utils::ui::Ui;
pub struct ProxiedProvider {
inner: Arc<dyn Provider>,
local_base_url: String,
}
impl ProxiedProvider {
pub fn wrap(inner: Arc<dyn Provider>, local_base_url: String) -> Self {
Self {
inner,
local_base_url,
}
}
}
impl crate::registry::Named for ProxiedProvider {
fn instance_id(&self) -> &str {
self.inner.instance_id()
}
}
#[async_trait]
impl Provider for ProxiedProvider {
fn name(&self) -> &str {
self.inner.name()
}
fn function_endpoints(&self) -> std::collections::HashMap<ModelFunction, Vec<ApiEndpoint>> {
self.inner.function_endpoints()
}
fn supported_api_types(&self) -> Vec<crate::providers::ApiType> {
self.inner.supported_api_types()
}
fn base_url(&self) -> &str {
&self.local_base_url
}
fn api_key(&self) -> Option<&Secret> {
None
}
fn verify_ssl(&self) -> bool {
true
}
fn supported_formats(&self) -> Vec<ModelFormat> {
self.inner.supported_formats()
}
fn can_run_model(&self, variant_format: &str, variant_precision: &str) -> bool {
self.inner.can_run_model(variant_format, variant_precision)
}
fn model_alias(&self, model_id: String, variant: Option<&ModelVariant>) -> Option<String> {
self.inner.model_alias(model_id, variant)
}
async fn health_check(&self) -> Result<HealthStatus, ProviderError> {
self.inner.health_check().await
}
fn custom_headers(&self) -> Option<std::collections::HashMap<String, Secret>> {
self.inner.custom_headers()
}
async fn pull_model(
&self,
model: &ModelMetadata,
variant: &ModelVariant,
ui: &dyn Ui,
) -> Result<PullResult, ProviderError> {
self.inner.pull_model(model, variant, ui).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
struct FakeProvider {
base_url: String,
api_key: Option<Secret>,
}
impl crate::registry::Named for FakeProvider {
fn instance_id(&self) -> &str {
"so fake!"
}
}
#[async_trait]
impl Provider for FakeProvider {
fn name(&self) -> &str {
"fake"
}
fn function_endpoints(&self) -> std::collections::HashMap<ModelFunction, Vec<ApiEndpoint>> {
std::collections::HashMap::new()
}
fn supported_api_types(&self) -> Vec<crate::providers::ApiType> {
vec![crate::providers::ApiType::OpenAI]
}
fn base_url(&self) -> &str {
&self.base_url
}
fn api_key(&self) -> Option<&Secret> {
self.api_key.as_ref()
}
fn verify_ssl(&self) -> bool {
true
}
fn custom_headers(&self) -> Option<std::collections::HashMap<String, Secret>> {
None
}
fn supported_formats(&self) -> Vec<ModelFormat> {
vec![]
}
async fn health_check(&self) -> Result<HealthStatus, ProviderError> {
unimplemented!("not exercised by these tests")
}
}
#[test]
fn wrap_points_connection_details_at_local_proxy_and_clears_api_key() {
let real: Arc<dyn Provider> = Arc::new(FakeProvider {
base_url: "https://api.example.com".to_string(),
api_key: Some(Secret("real-secret".to_string())),
});
let wrapped = ProxiedProvider::wrap(Arc::clone(&real), "http://127.0.0.1:9999".to_string());
assert_eq!(wrapped.base_url(), "http://127.0.0.1:9999");
assert!(
wrapped.api_key().is_none(),
"the launched process talks to the proxy and never sees the real credential"
);
assert!(wrapped.verify_ssl());
assert_eq!(
real.base_url(),
"https://api.example.com",
"wrapping must leave the real provider's own details alone, since the \
launch path reads them to register the route"
);
}
#[test]
fn everything_but_connection_details_delegates_to_inner() {
let real: Arc<dyn Provider> = Arc::new(FakeProvider {
base_url: "https://api.example.com".to_string(),
api_key: None,
});
let wrapped = ProxiedProvider::wrap(real, "http://127.0.0.1:9999".to_string());
use crate::registry::Named;
assert_eq!(wrapped.name(), "fake");
assert_eq!(wrapped.instance_id(), "so fake!");
assert_eq!(
wrapped.model_alias("granite".to_string(), None),
None,
"the alias must come from the real provider, so the route key registered \
and the name the launched process sends stay the same string"
);
}
}