winx_code_agent/tools/
file_write_or_edit.rs1mod commit;
7mod matcher;
8mod parser;
9mod report;
10
11#[cfg(test)]
12mod tests;
13
14use std::sync::Arc;
15
16use tokio::sync::Mutex;
17use tracing::instrument;
18
19use crate::errors::{Result, WinxError};
20use crate::state::bash_state::BashState;
21use crate::types::{normalize_thread_id, FileWriteOrEdit};
22pub(crate) use commit::{
23 commit_edit, ensure_parent_dirs, hash_content, invalidate_edit_read_permit, plan_edit,
24 plan_revision_edit, write_no_follow_if_unchanged,
25};
26
27#[cfg(feature = "fuzzing")]
28pub(crate) fn fuzz_apply_blocks(original: &str, blocks: &str) {
29 let _ = matcher::apply_blocks_with_unescape_retry(original, blocks);
30}
31
32#[instrument(level = "info", skip(bash_state_arc, file_write_or_edit))]
33pub async fn handle_tool_call(
34 bash_state_arc: &Arc<Mutex<Option<BashState>>>,
35 file_write_or_edit: FileWriteOrEdit,
36) -> Result<String> {
37 let mut bash_state_guard = bash_state_arc.lock().await;
38 {
39 let bash_state = bash_state_guard.as_ref().ok_or(WinxError::BashStateNotInitialized)?;
40 let thread_id = normalize_thread_id(&file_write_or_edit.thread_id);
41 if thread_id != bash_state.current_thread_id {
42 return Err(WinxError::ThreadIdMismatch(thread_id));
43 }
44 }
45
46 let mut state = bash_state_guard.take().ok_or(WinxError::BashStateNotInitialized)?;
47 let recovery_state = state.clone();
48 let joined = tokio::task::spawn_blocking(move || {
49 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
50 let planned = match plan_edit(
51 &state,
52 &file_write_or_edit.file_path,
53 file_write_or_edit.percentage_to_change,
54 &file_write_or_edit.text_or_search_replace_blocks,
55 true,
56 ) {
57 Ok(planned) => planned,
58 Err(error) => {
59 if error.is_search_match_conflict() {
60 invalidate_edit_read_permit(&mut state, &file_write_or_edit.file_path);
61 }
62 return Err(error);
63 }
64 };
65 commit_edit(&mut state, planned)
66 }))
67 .unwrap_or_else(|_| {
68 Err(WinxError::CommandExecutionError(
69 "FileWriteOrEdit panicked on the blocking worker".to_string(),
70 ))
71 });
72 (state, result)
73 })
74 .await;
75
76 match joined {
77 Ok((state, result)) => {
78 *bash_state_guard = Some(state);
79 result
80 }
81 Err(error) => {
82 *bash_state_guard = Some(recovery_state);
83 Err(WinxError::CommandExecutionError(format!(
84 "FileWriteOrEdit blocking task failed: {error}"
85 )))
86 }
87 }
88}