use bevy_ecs::prelude::*;
use bevy_ecs::reflect::{AppTypeRegistry, ReflectComponent};
use bevy_reflect::PartialReflect;
use rig_core::conversation::MessageId;
use super::agent::{Agent, AgentId, CacheKey, Conversation, Notice};
use super::model::Connection;
use super::restore::SavedKeys;
use super::session_log::{ReflectSaved, SessionLog, save_conversation};
#[derive(EntityEvent, Clone, Debug)]
pub struct ForkAgent {
#[event_target]
pub agent: Entity,
pub through: Option<MessageId>,
}
#[derive(Event, Clone, Debug)]
pub struct AgentForked {
pub from: Entity,
pub fork: Entity,
pub through: Option<MessageId>,
}
pub(crate) fn fork_agent(fork: On<ForkAgent>, mut commands: Commands) {
let (from, through) = (fork.agent, fork.through);
commands.queue(
move |world: &mut World| match fork_now(world, from, through) {
Ok(fork) => world.trigger(AgentForked {
from,
fork,
through,
}),
Err(why) => {
let why = format!("Could not fork the agent: {why}.");
world.write_message(Notice::error(from, why));
}
},
);
}
fn fork_now(world: &mut World, from: Entity, through: Option<MessageId>) -> Result<Entity, String> {
let id = world
.get::<AgentId>(from)
.cloned()
.ok_or("it is no agent")?;
let log = world
.get_resource::<SessionLog>()
.filter(|log| log.0.is_live())
.cloned()
.ok_or("the session is not logged")?;
let registry = world
.get_resource::<AppTypeRegistry>()
.cloned()
.ok_or("the app has no type registry")?;
if let Some(mut conversation) = world.get_mut::<Conversation>(from) {
let saved = save_conversation(&log, from, &id, conversation.bypass_change_detection());
world.write_message_batch(saved);
}
let to = AgentId::default();
let copy = log
.0
.fork_log(&id.0, &to.0, through)
.map_err(|failure| failure.to_string())?;
let registry = registry.read();
let saved: Vec<(&ReflectComponent, Box<dyn PartialReflect>)> = registry
.iter_with_data::<ReflectSaved>()
.filter_map(|(registration, _)| {
let component = registration.data::<ReflectComponent>()?;
let value = component.reflect(world.get_entity(from).ok()?)?;
let value = match value.reflect_clone() {
Ok(value) => value.into_partial_reflect(),
Err(_) => value.to_dynamic().ok()?,
};
Some((component, value))
})
.collect();
let key = CacheKey(CacheKey::of(world.get::<CacheKey>(from), &id).to_owned());
let connection = world.get::<Connection>(from).cloned();
let conversation = Conversation(copy.conversation);
let mut spawned = world.spawn((Name::new("agent"), Agent, to.clone(), conversation));
for (component, value) in &saved {
component.insert(&mut spawned, value.as_ref(), ®istry);
}
spawned.insert(key);
if let Some(connection) = connection {
spawned.insert(connection);
}
let fork = spawned.id();
let keys = world.get_resource::<SavedKeys>();
for key in copy.saved.keys() {
let path = keys.and_then(|keys| keys.path(key)).unwrap_or(key);
let registered = registry.get_with_type_path(path);
let component = registered.filter(|it| it.data::<ReflectSaved>().is_some());
let component = component.and_then(|it| it.data::<ReflectComponent>());
let held = |component: &ReflectComponent| {
let entity = world.get_entity(fork).ok();
entity.is_some_and(|entity| component.contains(entity))
};
if component.is_some_and(|component| !held(component)) {
log.0.save_value(&to.0, key, None, false);
}
}
Ok(fork)
}
#[cfg(test)]
mod tests;