use super::metadata::metadata_from_provider;
use super::{AiClient, ModelOptions, Provider, ProviderFactory};
use crate::types::{AiLibError, ChatCompletionRequest};
pub async fn list_models(client: &AiClient) -> Result<Vec<String>, AiLibError> {
client.chat_provider.list_models().await
}
pub fn switch_provider(client: &mut AiClient, provider: Provider) -> Result<(), AiLibError> {
let (base_url, transport) = if let Some(opts) = &client.connection_options {
let resolved_base_url = super::builder::resolve_base_url(provider, opts.base_url.clone())?;
let transport = if opts.proxy.is_some() || opts.timeout.is_some() {
let transport_config = crate::transport::HttpTransportConfig {
timeout: opts.timeout.unwrap_or(std::time::Duration::from_secs(30)),
proxy: opts.proxy.clone(),
pool_max_idle_per_host: None,
pool_idle_timeout: None,
};
Some(crate::transport::HttpTransport::new_with_config(transport_config)?.boxed())
} else {
None
};
(Some(resolved_base_url), transport)
} else {
let resolved_base_url = super::builder::resolve_base_url(provider, None)?;
(Some(resolved_base_url), None)
};
let api_key = client
.connection_options
.as_ref()
.and_then(|opts| opts.api_key.clone());
let chat_provider =
ProviderFactory::create_adapter(provider, api_key, base_url.clone(), transport)?;
let metadata = metadata_from_provider(
provider,
chat_provider.name().to_string(),
base_url,
client.custom_default_chat_model.clone(),
client.custom_default_multimodal_model.clone(),
);
client.chat_provider = chat_provider;
client.metadata = metadata;
Ok(())
}
pub fn build_simple_request<S: Into<String>>(
client: &AiClient,
prompt: S,
) -> ChatCompletionRequest {
let model = client
.custom_default_chat_model
.clone()
.or_else(|| client.metadata.default_chat_model().map(|s| s.to_string()))
.expect("AiClient metadata missing default chat model");
ChatCompletionRequest::new(
model,
vec![crate::types::Message {
role: crate::types::Role::User,
content: crate::types::common::Content::Text(prompt.into()),
function_call: None,
}],
)
}
pub fn build_simple_request_with_model<S: Into<String>>(
_client: &AiClient,
prompt: S,
model: S,
) -> ChatCompletionRequest {
ChatCompletionRequest::new(
model.into(),
vec![crate::types::Message {
role: crate::types::Role::User,
content: crate::types::common::Content::Text(prompt.into()),
function_call: None,
}],
)
}
pub fn build_multimodal_request<S: Into<String>>(
client: &AiClient,
prompt: S,
) -> Result<ChatCompletionRequest, AiLibError> {
let model = client
.custom_default_multimodal_model
.clone()
.or_else(|| {
client
.metadata
.default_multimodal_model()
.map(|s| s.to_string())
})
.ok_or_else(|| {
AiLibError::ConfigurationError(format!(
"No multimodal model available for provider {}",
client.provider_name()
))
})?;
Ok(ChatCompletionRequest::new(
model,
vec![crate::types::Message {
role: crate::types::Role::User,
content: crate::types::common::Content::Text(prompt.into()),
function_call: None,
}],
))
}
pub fn build_multimodal_request_with_model<S: Into<String>>(
_client: &AiClient,
prompt: S,
model: S,
) -> ChatCompletionRequest {
ChatCompletionRequest::new(
model.into(),
vec![crate::types::Message {
role: crate::types::Role::User,
content: crate::types::common::Content::Text(prompt.into()),
function_call: None,
}],
)
}
pub async fn quick_chat_text<P: Into<String>>(
provider: Provider,
prompt: P,
) -> Result<String, AiLibError> {
let client = AiClient::new(provider)?;
let req = client.build_simple_request(prompt.into());
let resp = client.chat_completion(req).await?;
resp.first_text().map(|s| s.to_string())
}
pub async fn quick_chat_text_with_model<P: Into<String>, M: Into<String>>(
provider: Provider,
prompt: P,
model: M,
) -> Result<String, AiLibError> {
let client = AiClient::new(provider)?;
let req = client.build_simple_request_with_model(prompt.into(), model.into());
let resp = client.chat_completion(req).await?;
resp.first_text().map(|s| s.to_string())
}
pub async fn quick_multimodal_text<P: Into<String>>(
provider: Provider,
prompt: P,
) -> Result<String, AiLibError> {
let client = AiClient::new(provider)?;
let req = client.build_multimodal_request(prompt.into())?;
let resp = client.chat_completion(req).await?;
resp.first_text().map(|s| s.to_string())
}
pub async fn quick_multimodal_text_with_model<P: Into<String>, M: Into<String>>(
provider: Provider,
prompt: P,
model: M,
) -> Result<String, AiLibError> {
let client = AiClient::new(provider)?;
let req = client.build_multimodal_request_with_model(prompt.into(), model.into());
let resp = client.chat_completion(req).await?;
resp.first_text().map(|s| s.to_string())
}
pub async fn quick_chat_text_with_options<P: Into<String>>(
provider: Provider,
prompt: P,
options: ModelOptions,
) -> Result<String, AiLibError> {
let client = AiClient::new(provider)?;
let model = if let Some(chat_model) = options.chat_model {
chat_model
} else {
provider.default_chat_model().to_string()
};
let req = client.build_simple_request_with_model(prompt.into(), model);
let resp = client.chat_completion(req).await?;
resp.first_text().map(|s| s.to_string())
}
pub async fn upload_file(client: &AiClient, path: &str) -> Result<String, AiLibError> {
let base_url = if let Some(opts) = &client.connection_options {
if let Some(b) = &opts.base_url {
Some(b.clone())
} else {
client.metadata.base_url().map(|s| s.to_string())
}
} else {
client.metadata.base_url().map(|s| s.to_string())
}
.ok_or_else(|| {
AiLibError::ConfigurationError(format!(
"No base URL available for provider {}",
client.provider_name()
))
})?;
let endpoint = client
.metadata
.upload_endpoint()
.map(|s| s.to_string())
.ok_or_else(|| {
AiLibError::UnsupportedFeature(format!(
"Provider {} does not expose an upload endpoint in OSS",
client.provider_name()
))
})?;
let upload_url = if base_url.ends_with('/') {
format!(
"{}{}",
base_url.trim_end_matches('/'),
endpoint.trim_start_matches('/')
)
} else {
format!("{}{}", base_url, endpoint)
};
crate::provider::utils::upload_file_with_transport(None, &upload_url, path, "file")
.await
.map_err(|err| err.with_context("client helpers upload_file"))
}