packages feed

langchain-hs-0.0.5.0: src/Langchain/Tool/FileSystem.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Langchain.Tool.FileSystem
Description : Standard File System Tools implementation
Copyright   : (c) 2025-2026 Tushar Adhatrao
License     : MIT
Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>
Stability   : experimental

File system tools (readFile, writeFile, listDir) built on Langchain.Core.Tool.
-}
module Langchain.Tool.FileSystem
  ( readFileTool
  , writeFileTool
  , listDirTool
  ) where

import Control.Exception (try)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Aeson
import Data.Aeson.Types (parseEither)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import System.Directory (listDirectory)

import Langchain.Core.Error (toolError)
import Langchain.Core.Tool (Tool (..), createTool)

-- | Read file content tool
readFileTool :: MonadIO m => Tool m
readFileTool =
  createTool
    "read_file"
    "Read text contents from a file path"
    ( object
        [ "type" .= ("object" :: Text)
        , "properties"
            .= object
              ["path" .= object ["type" .= ("string" :: Text)]]
        , "required" .= (["path"] :: [Text])
        ]
    )
    ( \case
        Object o -> case parseEither (.:? "path") o of
          Right (Just p) -> do
            eContent <- liftIO $ try (TIO.readFile (T.unpack p))
            case eContent of
              Left err -> pure $ Left $ toolError (T.pack $ show (err :: IOError)) (Just "read_file") Nothing
              Right txt -> pure $ Right txt
          _ -> pure $ Left $ toolError "Missing 'path' field" (Just "read_file") Nothing
        _ -> pure $ Left $ toolError "Invalid arguments object" (Just "read_file") Nothing
    )

-- | Write content to file tool
writeFileTool :: MonadIO m => Tool m
writeFileTool =
  createTool
    "write_file"
    "Write text contents to a file path"
    ( object
        [ "type" .= ("object" :: Text)
        , "properties"
            .= object
              [ "path" .= object ["type" .= ("string" :: Text)]
              , "content" .= object ["type" .= ("string" :: Text)]
              ]
        , "required" .= (["path", "content"] :: [Text])
        ]
    )
    ( \case
        Object o -> case (parseEither (.:? "path") o, parseEither (.:? "content") o) of
          (Right (Just p), Right (Just content)) -> do
            eRes <- liftIO $ try (TIO.writeFile (T.unpack p) content)
            case eRes of
              Left err -> pure $ Left $ toolError (T.pack $ show (err :: IOError)) (Just "write_file") Nothing
              Right () -> pure $ Right ("Successfully wrote to " <> p)
          _ -> pure $ Left $ toolError "Missing 'path' or 'content' field" (Just "write_file") Nothing
        _ -> pure $ Left $ toolError "Invalid arguments object" (Just "write_file") Nothing
    )

-- | List directory contents tool
listDirTool :: MonadIO m => Tool m
listDirTool =
  createTool
    "list_directory"
    "List files and subdirectories in a directory path"
    ( object
        [ "type" .= ("object" :: Text)
        , "properties"
            .= object
              ["path" .= object ["type" .= ("string" :: Text)]]
        , "required" .= (["path"] :: [Text])
        ]
    )
    ( \case
        Object o -> case parseEither (.:? "path") o of
          Right (Just p) -> do
            eFiles <- liftIO $ try (listDirectory (T.unpack p))
            case eFiles of
              Left err -> pure $ Left $ toolError (T.pack $ show (err :: IOError)) (Just "list_directory") Nothing
              Right files -> pure $ Right (T.unlines $ map T.pack files)
          _ -> pure $ Left $ toolError "Missing 'path' field" (Just "list_directory") Nothing
        _ -> pure $ Left $ toolError "Invalid arguments object" (Just "list_directory") Nothing
    )