swink_agent/tools/
read_file.rs1use 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
11pub struct ReadFileTool {
13 schema: Value,
14}
15
16impl ReadFileTool {
17 #[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 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}