2026-07-20 09:04:57 +07:00
|
|
|
//! Read a file from the workspace.
|
|
|
|
|
|
|
|
|
|
use crate::tools::{resolve_path, Tool, ToolCtx};
|
|
|
|
|
use anyhow::Result;
|
|
|
|
|
use serde_json::{json, Value};
|
|
|
|
|
use std::fs;
|
2026-07-20 15:53:20 +07:00
|
|
|
use tracing::{info, instrument, warn};
|
2026-07-20 09:04:57 +07:00
|
|
|
|
2026-07-20 15:53:20 +07:00
|
|
|
/// Read the contents of a file.
|
|
|
|
|
///
|
|
|
|
|
/// Flow: resolve path → check existence and type → read file content → return.
|
2026-07-20 09:04:57 +07:00
|
|
|
pub struct Read;
|
|
|
|
|
|
|
|
|
|
impl Tool for Read {
|
|
|
|
|
fn name(&self) -> &'static str {
|
|
|
|
|
"read"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn description(&self) -> &'static str {
|
|
|
|
|
"Read the contents of a file"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parameters(&self) -> Value {
|
|
|
|
|
json!({
|
|
|
|
|
"type": "object",
|
|
|
|
|
"properties": {
|
|
|
|
|
"path": {
|
|
|
|
|
"type": "string",
|
|
|
|
|
"description": "Path to the file to read (relative to workspace root)"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"required": ["path"]
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-20 15:53:20 +07:00
|
|
|
#[instrument(skip(self, ctx, args))]
|
2026-07-20 09:04:57 +07:00
|
|
|
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
|
|
|
|
|
let rel = crate::tools::arg_str(args, "path")?;
|
|
|
|
|
let path = resolve_path(&ctx.workspaces, &rel)?;
|
|
|
|
|
if !path.exists() {
|
2026-07-20 15:53:20 +07:00
|
|
|
warn!(rel = %rel, "read target does not exist");
|
2026-07-20 09:04:57 +07:00
|
|
|
anyhow::bail!("file '{rel}' does not exist");
|
|
|
|
|
}
|
|
|
|
|
if !path.is_file() {
|
2026-07-20 15:53:20 +07:00
|
|
|
warn!(rel = %rel, "read target is not a file");
|
2026-07-20 09:04:57 +07:00
|
|
|
anyhow::bail!("'{rel}' is not a file");
|
|
|
|
|
}
|
|
|
|
|
let content = fs::read_to_string(&path)?;
|
2026-07-20 15:53:20 +07:00
|
|
|
info!(rel = %rel, bytes = content.len(), "file read");
|
2026-07-20 09:04:57 +07:00
|
|
|
Ok(content)
|
|
|
|
|
}
|
|
|
|
|
}
|