Skip to main content

oino_tools/
write.rs

1//! `write`.
2
3use futures::FutureExt;
4use futures::future::BoxFuture;
5use oino_agent::{
6    AgentTool, AgentToolResult, ToolDefinitionMeta, ToolError, ToolInvocation, UpdateSink,
7};
8use tokio_util::sync::CancellationToken;
9
10use crate::config::ToolConfig;
11use crate::info::write_info;
12use crate::path_utils::resolve_to_cwd;
13use crate::{aborted, args, node_path};
14
15/// The `write` tool.
16#[derive(Debug)]
17pub struct WriteTool {
18    meta: ToolDefinitionMeta,
19    config: ToolConfig,
20}
21
22impl WriteTool {
23    /// A `write` tool.
24    #[must_use]
25    pub fn new(config: ToolConfig) -> Self {
26        Self {
27            meta: write_info().definition_meta(),
28            config,
29        }
30    }
31}
32
33impl AgentTool for WriteTool {
34    fn meta(&self) -> &ToolDefinitionMeta {
35        &self.meta
36    }
37
38    fn execute(
39        &self,
40        call: ToolInvocation,
41        _updates: UpdateSink,
42        cancel: CancellationToken,
43    ) -> BoxFuture<'static, Result<AgentToolResult, ToolError>> {
44        let config = self.config.clone();
45        async move { run(&config, &call, &cancel).await }.boxed()
46    }
47}
48
49async fn run(
50    config: &ToolConfig,
51    call: &ToolInvocation,
52    cancel: &CancellationToken,
53) -> Result<AgentToolResult, ToolError> {
54    let path = args::required_str(&call.args, "path")?;
55    let content = args::required_str(&call.args, "content")?;
56    let absolute = resolve_to_cwd(path, &config.effective_cwd())
57        .map_err(|error| ToolError::new(error.to_string()))?;
58    let dir = node_path::dirname(&absolute);
59    let env = config.env.as_ref();
60    // Abort is checked after each step, never mid-operation, so the queue stays locked until
61    // the file system call settled.
62    let check = || {
63        if cancel.is_cancelled() {
64            Err(aborted())
65        } else {
66            Ok(())
67        }
68    };
69    config
70        .mutation_queue
71        .run(env, &absolute, async {
72            check()?;
73            env.mkdir_p(&dir)
74                .await
75                .map_err(|error| ToolError::new(error.to_string()))?;
76            check()?;
77            env.write_file(&absolute, content.as_bytes())
78                .await
79                .map_err(|error| ToolError::new(error.to_string()))?;
80            check()?;
81            Ok(AgentToolResult::text(format!(
82                "Successfully wrote to {path}"
83            )))
84        })
85        .await
86}