beet_agent 0.0.8

ECS agentic workflow patterns
use crate::prelude::*;
use beet_core::prelude::*;
use std::fmt::Debug;
use std::hash::Hash;



pub struct MessageView<'a> {
	pub actor: ActorView<'a>,
	pub message: &'a Message,
	pub content: Vec<ContentView<'a>>,
}

/// A role relative to the actor:
/// -  If the actor owns the content the role is [`Role::This`],
/// - otherwise if the owner has a [`DeveloperRole`] component the role is [`Role::Developer`].
/// Any other case is [`Role::Other`] which may be a user or another agent
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelativeRole {
	/// The role is this actor,
	This,
	Developer,
	Other,
}

impl RelativeRole {}

/// Indicate it is 'your turn'
#[derive(EntityEvent)]
pub struct MessageRequest(pub Entity);



/// Marker component indicating the root entity for an actor's message.
/// Messages must be (possibly nested) descendents of an [`Actor`], and may
/// contain Content either in its entity its descendents.
#[derive(Debug, Clone, Component)]
pub struct Message {
	pub created: Instant,
}

impl Default for Message {
	fn default() -> Self {
		Self {
			created: Instant::now(),
		}
	}
}


/// Added to a [`Message`] when it is finished, and no more content
/// will be added to it.
#[derive(Debug, Clone, Component)]
pub struct MessageComplete;


pub struct MessageSpawner<T: Hash> {
	pub world: AsyncWorld,
	pub message: Entity,
	pub content_map: HashMap<T, Entity>,
	pub finished_content: Vec<T>,
}
impl<T: Hash + Eq + Debug> MessageSpawner<T> {
	pub async fn spawn(world: AsyncWorld, actor: Entity) -> Result<Self> {
		let message = world
			.spawn_then((Message::default(), ChildOf(actor)))
			.await
			.id();


		Self {
			world,
			message,
			content_map: HashMap::default(),
			finished_content: Vec::new(),
		}
		.xok()
	}
	pub async fn add(
		&mut self,
		key: T,
		content: impl Bundle,
	) -> Result<&mut Self> {
		if self.content_map.contains_key(&key) {
			bevybail!("Duplicate output index: {key:?}");
		} else {
			let entity = self
				.world
				.spawn_then((ChildOf(self.message), content))
				.await
				.id();
			self.content_map.insert(key, entity);
		}
		Ok(self)
	}

	/// Inserts the bundle, creating the entity if it doesnt exist
	pub async fn insert(
		&mut self,
		key: T,
		bundle: impl Bundle,
	) -> Result<&mut Self> {
		if let Ok(entity) = self.get_entity(&key) {
			self.world.entity(entity).insert_then(bundle).await;
		} else {
			self.add(key, bundle).await?;
		}
		Ok(self)
	}

	fn get_entity(&self, key: &T) -> Result<Entity> {
		self.content_map
			.get(key)
			.copied()
			.ok_or_else(|| bevyhow!("Missing entity for index: {key:?}"))
	}

	pub async fn add_or_delta(
		&mut self,
		key: T,
		text: impl AsRef<str>,
	) -> Result<&mut Self> {
		if self.content_map.contains_key(&key) {
			self.text_delta(key, text).await
		} else {
			self.add(key, TextContent::new(text)).await
		}
	}

	pub async fn text_delta(
		&mut self,
		key: T,
		text: impl AsRef<str>,
	) -> Result<&mut Self> {
		self.world
			.entity(self.get_entity(&key)?)
			.trigger(TextDelta::new(text))
			.await;
		Ok(self)
	}

	pub async fn finish_content(&mut self, key: T) -> Result<&mut Self> {
		self.world
			.entity(self.get_entity(&key)?)
			.insert_then(ContentEnded::default())
			.await;
		self.finished_content.push(key);
		Ok(self)
	}
	pub async fn finish_message(&mut self) -> Result {
		let unfinished = self
			.content_map
			.iter()
			.filter(|(k, _)| !self.finished_content.contains(k))
			.map(|(_, v)| *v)
			.collect::<Vec<_>>();

		let message = self.message;
		self.world
			.with_then(move |world| {
				for entity in unfinished {
					world.entity_mut(entity).insert(ContentEnded::default());
				}
				world.entity_mut(message).insert(MessageComplete);
			})
			.await;
		Ok(())
	}
}