This is a huge patch which does some major refactoring like: - renaming "Image Generation" to "Image Creation" in most places, to better match its new command (`!bai image create`) - relocating image creation command (`!bai image` -> `!bai image create`), so it wouldn't conflict with the new image editing command (`!bai image edit`) - introducing a new image editing command (`!bai image edit`), which is meant to work only with the OpenAI provider, but doesn't fully work yet due to https://github.com/64bit/async-openai/issues/364, though a next patch will fix it - adding support for reading images off of Matrix conversations and forwarding them to text conversations. Works for OpenAI, but not for Anthropic yet (requires custom patches) and not for OpenAI-Compat (no support for images there) - relocating some utils around (base64, mime)
141 lines
4.0 KiB
Rust
141 lines
4.0 KiB
Rust
use crate::{
|
|
agent::{
|
|
AgentInstance, AgentPurpose, ControllerTrait, Manager as AgentManager, PublicIdentifier,
|
|
},
|
|
entity::RoomConfigContext,
|
|
strings,
|
|
};
|
|
|
|
#[derive(Debug)]
|
|
pub struct AgentForPurposeDeterminationInfo {
|
|
pub instance: AgentInstance,
|
|
pub configuration_source: AgentForPurposeDeterminationInfoConfigurationSource,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum AgentForPurposeDeterminationInfoConfigurationSource {
|
|
Room,
|
|
Global,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum AgentForPurposeDeterminationError {
|
|
Unknown(String),
|
|
|
|
NoneConfigured,
|
|
|
|
ConfiguredButMissing(PublicIdentifier),
|
|
|
|
ConfiguredButLacksSupport(PublicIdentifier),
|
|
}
|
|
|
|
pub async fn get_effective_agent_for_purpose(
|
|
agent_manager: &AgentManager,
|
|
room_config_context: &RoomConfigContext,
|
|
agent_purpose: AgentPurpose,
|
|
) -> Result<AgentForPurposeDeterminationInfo, AgentForPurposeDeterminationError> {
|
|
let (agent_identifier, configuration_source) =
|
|
match get_effective_room_agent_identifier_for_purpose(room_config_context, agent_purpose)
|
|
.await
|
|
{
|
|
Ok((agent_identifier, configuration_source)) => {
|
|
(agent_identifier, configuration_source)
|
|
}
|
|
Err(err) => {
|
|
return Err(AgentForPurposeDeterminationError::Unknown(err));
|
|
}
|
|
};
|
|
|
|
let Some(agent_identifier) = agent_identifier else {
|
|
return Err(AgentForPurposeDeterminationError::NoneConfigured);
|
|
};
|
|
|
|
let agents = agent_manager.available_room_agents_by_room_config_context(room_config_context);
|
|
|
|
let Some(agent_instance) = agents.iter().find(|a| *a.identifier() == agent_identifier) else {
|
|
return Err(AgentForPurposeDeterminationError::ConfiguredButMissing(
|
|
agent_identifier,
|
|
));
|
|
};
|
|
|
|
let agent_instance = agent_instance.clone();
|
|
|
|
let supports_purpose = agent_instance.controller().supports_purpose(agent_purpose);
|
|
|
|
if !supports_purpose {
|
|
return Err(AgentForPurposeDeterminationError::ConfiguredButLacksSupport(agent_identifier));
|
|
}
|
|
|
|
Ok(AgentForPurposeDeterminationInfo {
|
|
instance: agent_instance,
|
|
configuration_source,
|
|
})
|
|
}
|
|
|
|
async fn get_effective_room_agent_identifier_for_purpose(
|
|
room_config_context: &RoomConfigContext,
|
|
purpose: AgentPurpose,
|
|
) -> Result<
|
|
(
|
|
Option<PublicIdentifier>,
|
|
AgentForPurposeDeterminationInfoConfigurationSource,
|
|
),
|
|
String,
|
|
> {
|
|
let (agent_id, configuration_source) =
|
|
get_effective_room_agent_raw_id_for_purpose(room_config_context, purpose).await;
|
|
|
|
let Some(agent_id) = agent_id else {
|
|
return Ok((None, configuration_source));
|
|
};
|
|
|
|
let agent_identifier = match PublicIdentifier::from_str(agent_id.as_str()) {
|
|
Some(agent_identifier) => agent_identifier,
|
|
None => return Err(strings::agent::invalid_id_generic()),
|
|
};
|
|
|
|
Ok((Some(agent_identifier), configuration_source))
|
|
}
|
|
|
|
async fn get_effective_room_agent_raw_id_for_purpose(
|
|
room_config_context: &RoomConfigContext,
|
|
purpose: AgentPurpose,
|
|
) -> (
|
|
Option<String>,
|
|
AgentForPurposeDeterminationInfoConfigurationSource,
|
|
) {
|
|
let agent_id = room_config_context
|
|
.room_config
|
|
.settings
|
|
.handler
|
|
.get_by_purpose_with_catch_all_fallback(purpose);
|
|
|
|
if let Some(agent_id) = agent_id {
|
|
return (
|
|
Some(agent_id),
|
|
AgentForPurposeDeterminationInfoConfigurationSource::Room,
|
|
);
|
|
}
|
|
|
|
tracing::trace!(
|
|
?purpose,
|
|
"No specific agent found for purpose in room, falling back to global.",
|
|
);
|
|
|
|
(
|
|
get_global_agent_id_for_purpose(room_config_context, purpose).await,
|
|
AgentForPurposeDeterminationInfoConfigurationSource::Global,
|
|
)
|
|
}
|
|
|
|
async fn get_global_agent_id_for_purpose(
|
|
room_config_context: &RoomConfigContext,
|
|
purpose: AgentPurpose,
|
|
) -> Option<String> {
|
|
room_config_context
|
|
.global_config
|
|
.fallback_room_settings
|
|
.handler
|
|
.get_by_purpose_with_catch_all_fallback(purpose)
|
|
}
|