Skip to main content

swink_agent/tools/
read_file.rs

1//! Built-in tool for reading file contents.
2
3use schemars::JsonSchema;
4use serde::Deserialize;
5use serde_json::Value;
6use tokio_util::sync::CancellationToken;
7
8use super::MAX_OUTPUT_BYTES;
9use crate::tool::{AgentTool, AgentToolResult, ToolFuture, validated_schema_for};
10
11/// Built-in tool that reads a file and returns its contents.
12pub struct ReadFileTool {
13    schema: Value,
14}
15
16impl ReadFileTool {
17    /// Create a new `ReadFileTool`.
18    #[must_use]
19    pub fn new() -> Self {
20        Self {
21            schema: validated_schema_for::<Params>(),
22        }
23    }
24}
25
26impl Default for ReadFileTool {
27    fn default() -> Self {
28        Self::new()
29    }
30}
31
32#[derive(Deserialize, JsonSchema)]
33#[schemars(deny_unknown_fields)]
34struct Params {
35    /// Absolute path to the file to read.
36    path: String,
37}
38
39#[allow(clippy::unnecessary_literal_bound)]
40impl AgentTool for ReadFileTool {
41    fn name(&self) -> &str {
42        "read_file"
43    }
44
45    fn label(&self) -> &str {
46        "Read File"
47    }
48
49    fn description(&self) -> &str {
50        "Read a file and return its contents."
51    }
52
53    fn parameters_schema(&self) -> &Value {
54        &self.schema
55    }
56
57    fn execute(
58        &self,
59        _tool_call_id: &str,
60        params: Value,
61        cancellation_token: CancellationToken,
62        _on_update: Option<Box<dyn Fn(AgentToolResult) + Send + Sync>>,
63        _state: std::sync::Arc<std::sync::RwLock<crate::SessionState>>,
64        _credential: Option<crate::credential::ResolvedCredential>,
65    ) -> ToolFuture<'_> {
66        Box::pin(async move {
67            let parsed: Params = match serde_json::from_value(params) {
68                Ok(p) => p,
69                Err(e) => return AgentToolResult::error(format!("invalid parameters: {e}")),
70            };
71
72            if cancellation_token.is_cancelled() {
73                return AgentToolResult::error("cancelled");
74            }
75
76            match tokio::fs::read_to_string(&parsed.path).await {
77                Ok(mut content) => {
78                    if content.len() > MAX_OUTPUT_BYTES {
79                        content.truncate(MAX_OUTPUT_BYTES);
80                        content.push_str("\n[truncated]");
81                    }
82                    AgentToolResult::text(content)
83                }
84                Err(e) => {
85                    AgentToolResult::error(format!("failed to read file {}: {e}", parsed.path))
86                }
87            }
88        })
89    }
90}