use std::collections::BTreeMap;
use crate::models::SystemBlock;
use super::fragment::{FragmentId, FragmentRender, FragmentRole, ModelContextFragment};
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[allow(dead_code)] pub struct WorldStateDiff {
pub updated: Vec<ModelContextFragment>,
pub retained: Vec<String>,
pub cleared: Vec<String>,
}
impl WorldStateDiff {
#[must_use]
#[allow(dead_code)] pub fn render_incremental_text(&self) -> String {
let mut parts = Vec::with_capacity(self.updated.len() + self.cleared.len());
for fragment in &self.updated {
parts.push(fragment.render_marked());
}
for marker in &self.cleared {
parts.push(format!("{marker}\n[cleared]"));
}
parts.join("\n\n")
}
#[must_use]
#[allow(dead_code)] pub fn is_noop(&self) -> bool {
self.updated.is_empty() && self.cleared.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct WorldState {
fragments: BTreeMap<FragmentId, ModelContextFragment>,
}
impl WorldState {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn upsert(&mut self, fragment: ModelContextFragment) -> FragmentRender {
let previous = self.fragments.get(&fragment.id).cloned();
let render = fragment.render_diff(previous.as_ref());
if matches!(render, FragmentRender::Updated { .. }) {
self.fragments.insert(fragment.id, fragment);
}
render
}
#[allow(dead_code)] pub fn clear(&mut self, id: FragmentId) -> FragmentRender {
match self.fragments.remove(&id) {
Some(prev) => FragmentRender::Cleared {
marker: prev.marker.to_string(),
},
None => FragmentRender::Cleared {
marker: id.marker().to_string(),
},
}
}
#[must_use]
#[allow(dead_code)] pub fn get(&self, id: FragmentId) -> Option<&ModelContextFragment> {
self.fragments.get(&id)
}
#[must_use]
pub fn len(&self) -> usize {
self.fragments.len()
}
#[must_use]
#[allow(dead_code)] pub fn is_empty(&self) -> bool {
self.fragments.is_empty()
}
#[must_use]
#[allow(dead_code)] pub fn render_full(&self) -> String {
self.fragments
.values()
.map(ModelContextFragment::render_marked)
.collect::<Vec<_>>()
.join("\n\n")
}
#[must_use]
#[allow(dead_code)] pub fn render_diff(&self, previous: Option<&WorldState>) -> WorldStateDiff {
let Some(previous) = previous else {
return WorldStateDiff {
updated: self.fragments.values().cloned().collect(),
retained: Vec::new(),
cleared: Vec::new(),
};
};
let mut diff = WorldStateDiff::default();
for id in FragmentId::all() {
match (previous.fragments.get(id), self.fragments.get(id)) {
(Some(prev), Some(next)) => match next.render_diff(Some(prev)) {
FragmentRender::Unchanged { marker, .. } => diff.retained.push(marker),
FragmentRender::Updated { fragment } => diff.updated.push(fragment),
FragmentRender::Cleared { marker } => diff.cleared.push(marker),
},
(None, Some(next)) => diff.updated.push(next.clone()),
(Some(prev), None) => diff.cleared.push(prev.marker.to_string()),
(None, None) => {}
}
}
diff
}
#[must_use]
pub fn with_workspace(mut self, body: impl Into<String>) -> Self {
self.upsert(ModelContextFragment::new(
FragmentId::Workspace,
FragmentRole::Workspace,
body,
));
self
}
#[must_use]
pub fn with_permissions(mut self, body: impl Into<String>) -> Self {
self.upsert(ModelContextFragment::new(
FragmentId::Permissions,
FragmentRole::Permissions,
body,
));
self
}
#[must_use]
pub fn with_route(mut self, body: impl Into<String>) -> Self {
self.upsert(ModelContextFragment::new(
FragmentId::Route,
FragmentRole::Route,
body,
));
self
}
#[must_use]
pub fn with_agent_topology(mut self, body: impl Into<String>) -> Self {
self.upsert(ModelContextFragment::new(
FragmentId::AgentTopology,
FragmentRole::AgentTopology,
body,
));
self
}
#[must_use]
pub fn with_skills_tools(mut self, body: impl Into<String>) -> Self {
self.upsert(ModelContextFragment::new(
FragmentId::SkillsTools,
FragmentRole::SkillsTools,
body,
));
self
}
#[must_use]
pub fn with_token_budget(mut self, body: impl Into<String>) -> Self {
self.upsert(ModelContextFragment::new(
FragmentId::TokenBudget,
FragmentRole::TokenBudget,
body,
));
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorldStateSnapshot {
pub constitution: String,
pub world_state: WorldState,
}
impl WorldStateSnapshot {
#[must_use]
pub fn to_system_blocks(&self) -> Vec<SystemBlock> {
let mut blocks = Vec::with_capacity(1 + self.world_state.len());
blocks.push(SystemBlock {
block_type: "text".to_string(),
text: self.constitution.trim().to_string(),
cache_control: None,
});
for fragment in self.world_state.fragments.values() {
blocks.push(SystemBlock {
block_type: "text".to_string(),
text: fragment.render_marked(),
cache_control: None,
});
}
blocks
}
#[must_use]
#[allow(dead_code)] pub fn render_text(&self) -> String {
let world = self.world_state.render_full();
if world.is_empty() {
self.constitution.trim().to_string()
} else {
format!("{}\n\n{}", self.constitution.trim(), world)
}
}
#[must_use]
#[allow(dead_code)] pub fn render_world_diff(&self, previous: Option<&WorldState>) -> WorldStateDiff {
self.world_state.render_diff(previous)
}
}