use std::{path::PathBuf, sync::Arc};
use bamboo_agent_core::AgentError;
use bamboo_config::Config;
use bamboo_domain::ProviderModelRef;
use bamboo_llm::{LLMProvider, ResolvedModel};
use super::vision_target::{configured_instance, validate_target};
use super::LegacyVisionModelResolver;
fn selected_target(
config: &Config,
active_provider: &str,
requested_model: Option<&str>,
from_config: bool,
) -> Result<ProviderModelRef, AgentError> {
if requested_model.is_some_and(|model| model.trim().is_empty())
|| (requested_model.is_none() && !from_config)
{
return Err(AgentError::LLM(
"Explicit legacy Vision model is missing or empty; original images were preserved and no image was sent.".into(),
));
}
let configured = config.defaults.as_ref().map(|defaults| {
defaults
.vision
.as_ref()
.or(defaults.fast.as_ref())
.unwrap_or(&defaults.chat)
.clone()
});
let requested = requested_model
.map(str::trim)
.filter(|model| !model.is_empty());
if let Some(target) = configured.as_ref() {
if requested.is_none() {
if target.provider != active_provider
&& config
.defaults
.as_ref()
.is_some_and(|defaults| defaults.vision.is_none())
{
return Err(AgentError::LLM(
"Legacy image conversion needs an explicit Vision role to use another account; Fast/Chat alone do not authorize that image destination. Original images were preserved and no image was sent.".into(),
));
}
return Ok(target.clone());
}
}
let model = requested
.map(str::to_string)
.or_else(|| {
configured_instance(config, active_provider).and_then(|instance| {
let copilot_default =
(instance.provider_type == "copilot").then(|| "gpt-4o".to_string());
instance
.vision_model
.or(instance.fast_model)
.or(instance.model)
.or(copilot_default)
})
})
.or_else(|| {
(config.defaults.is_none() && config.effective_default_provider() == active_provider)
.then(|| config.get_vision_model())
.flatten()
})
.ok_or_else(|| {
AgentError::LLM(
"Legacy Vision fallback requires a selected model; original images were preserved."
.into(),
)
})?;
let mut target = ProviderModelRef::new(active_provider, model);
target.reasoning_effort = config.reasoning_effort_for_key(active_provider);
Ok(target)
}
pub(crate) fn legacy_vision_resolver(
config: Config,
active_provider: String,
active_client: Arc<dyn LLMProvider>,
requested_model: Option<String>,
app_data_dir: Option<PathBuf>,
from_config: bool,
source: Option<Arc<bamboo_llm::provider_registry::LegacyVisionProviderSource>>,
) -> LegacyVisionModelResolver {
Arc::new(move || {
let config = config.clone();
let active_provider = active_provider.clone();
let active_client = active_client.clone();
let requested_model = requested_model.clone();
let app_data_dir = app_data_dir.clone();
let source = source.clone();
Box::pin(async move {
let target = selected_target(
&config,
&active_provider,
requested_model.as_deref(),
from_config,
)?;
let instance = if target.provider == active_provider
&& configured_instance(&config, &target.provider).is_none()
{
None
} else {
Some(validate_target(&config, &target)?)
};
let provider = if target.provider == active_provider {
if from_config && requested_model.is_none() {
let changed = || {
AgentError::LLM(
"Legacy Vision provider source changed; original images were preserved and no image was sent.".into(),
)
};
source
.as_ref()
.ok_or_else(changed)?
.validate(&config, &active_provider, &active_client)
.map_err(|_| changed())?;
}
active_client
} else {
bamboo_llm::provider_factory::create_provider_from_instance(
&config, &instance.expect("cross-account target requires exact configured metadata"),
app_data_dir.unwrap_or_else(bamboo_config::paths::bamboo_dir),
).await.map_err(|_| AgentError::LLM("Configured legacy Vision provider could not be initialized; original images were preserved and no image was sent.".into()))?
};
Ok(ResolvedModel::from_ref(provider, &target))
})
})
}