use std::sync::Arc;
use aion_core::{
AssistantDocumentEditError, AssistantDocumentEditOp, AssistantSessionEvent, AssistantSessionId,
apply_document_edits,
};
use tokio::sync::Mutex;
use super::error::AssistantSessionError;
use super::registry::AssistantSessions;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct DocumentEditReceipt {
pub(crate) revision: u64,
pub(crate) path: String,
pub(crate) applied: usize,
}
#[derive(Debug)]
pub(crate) enum DocumentEditRefusal {
NoDocument,
Edits(AssistantDocumentEditError),
Session(AssistantSessionError),
}
impl AssistantSessions {
pub(crate) async fn record_document_edit(
&self,
session_id: AssistantSessionId,
edits: Vec<AssistantDocumentEditOp>,
) -> Result<DocumentEditReceipt, DocumentEditRefusal> {
let lock = self.document_edit_lock(session_id);
let guard = lock.lock().await;
let projection = self
.projection(session_id)
.await
.map_err(DocumentEditRefusal::Session)?;
let Some(document) = projection
.latest_context
.as_ref()
.and_then(|context| context.document.as_ref())
else {
return Err(DocumentEditRefusal::NoDocument);
};
apply_document_edits(&document.text, &edits).map_err(DocumentEditRefusal::Edits)?;
let revision = projection.document_revision.saturating_add(1);
let receipt = DocumentEditReceipt {
revision,
path: document.path.clone(),
applied: edits.len(),
};
self.recorder(session_id)
.record(AssistantSessionEvent::DocumentEdit { edits, revision })
.await
.map_err(DocumentEditRefusal::Session)?;
drop(guard);
Ok(receipt)
}
fn document_edit_lock(&self, session_id: AssistantSessionId) -> Arc<Mutex<()>> {
self.document_edit_locks()
.entry(session_id)
.or_insert_with(|| Arc::new(Mutex::new(())))
.value()
.clone()
}
}