use aion_core::AssistantDocumentEditOp;
use aion_mcp::tools::service::{ToolFailure, ToolOutcome};
use serde::Deserialize;
use serde_json::{Value, json};
use crate::assistant::sessions::AssistantSessions;
use crate::assistant::sessions::document_edits::DocumentEditRefusal;
use super::caller::AssistantSessionCaller;
#[derive(Deserialize)]
struct EditArguments {
edits: Vec<AssistantDocumentEditOp>,
}
pub(crate) async fn assistant_document_edit(
sessions: &AssistantSessions,
caller: &AssistantSessionCaller,
arguments: Value,
) -> Result<ToolOutcome, ToolFailure> {
let arguments: EditArguments = serde_json::from_value(arguments).map_err(|error| {
ToolFailure::new(
format!(
"the arguments do not match the published shape — expected \
{{\"edits\": [{{\"old_string\": …, \"new_string\": …}}]}}: {error}"
),
json!({ "code": "invalid_arguments" }),
)
})?;
if arguments.edits.is_empty() {
return Err(ToolFailure::new(
"the batch is empty — send at least one edit, or send none at all.".to_owned(),
json!({ "code": "empty_batch" }),
));
}
let receipt = sessions
.record_document_edit(caller.session_id(), arguments.edits)
.await
.map_err(|refusal| match refusal {
DocumentEditRefusal::NoDocument => ToolFailure::new(
"there is no document to edit: the operator has not shared one from an editor. \
This does NOT mean their screen is empty — call `assistant_context` to see what \
they have shared."
.to_owned(),
json!({ "code": "no_document" }),
),
DocumentEditRefusal::Edits(error) => ToolFailure::new(
format!(
"the batch does not apply to the document as it now stands, and NONE of it \
was applied: {error}"
),
json!({ "code": "edit_rejected" }),
),
DocumentEditRefusal::Session(error) => ToolFailure::new(
format!(
"the edit could not be recorded for this conversation: {error}. The document \
is untouched."
),
json!({ "code": "transcript_unwritable" }),
),
})?;
Ok(ToolOutcome {
structured: json!({
"applied": receipt.applied,
"revision": receipt.revision,
"path": receipt.path,
}),
summary: format!(
"Applied {} edit{} to `{}` — the operator sees them in their editor now and decides \
whether to keep them. The shared document is at revision {}.",
receipt.applied,
if receipt.applied == 1 { "" } else { "s" },
receipt.path,
receipt.revision,
),
})
}