First Types

This commit is contained in:
Nicole Dresselhaus 2025-03-10 08:14:00 +01:00
parent cbe4943e3f
commit 2c5f66d98a
9 changed files with 336 additions and 119 deletions

1
.gitignore vendored
View File

@ -22,4 +22,3 @@ cabal.project.local
cabal.project.local~
.HTF/
.ghc.environment.*

View File

@ -1,8 +1,53 @@
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module Main where
import qualified MyLib (someFunc)
import Agent.Server.Config (Config, configFile, loadConfigOrDefault, saveConfig)
import Agent.Tools.Time ()
import Agent.Tools.Tool (Tool, getToolListTH, toMarkdownDL)
import Control.Concurrent (threadDelay)
import Control.Exception (AsyncException (UserInterrupt), bracket, catch)
import Control.Monad (when)
import qualified Data.ByteString as BS
import Data.Default
import Data.Either (fromRight, isRight)
import Data.Either.Extra (fromRight')
import Data.Proxy
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.Yaml (decodeFileThrow)
import qualified Data.Yaml as YAML
import Language.Haskell.TH
import System.IO (hFlush, stdout)
main :: IO ()
main = do
putStrLn "Hello, Haskell!"
MyLib.someFunc
(conf :: Config) <- (configFile >>= maybe (return def) decodeFileThrow)
let availableTools = ($(getToolListTH) :: [Tool])
putStrLn "# HAgent\n"
putStrLn "\n## Cofig\n"
BS.putStr $ (YAML.encode conf) <> "\n"
putStrLn "## Available Tools\n"
mapM_ T.putStrLn $ toMarkdownDL <$> availableTools
bracket acquireResource releaseResource $ \_ ->
mainLoop `catch` handler
where
acquireResource :: IO ()
acquireResource = putStrLn "Acquiring resource..."
releaseResource :: () -> IO ()
releaseResource _ = putStrLn "Releasing resource..."
mainLoop :: IO ()
mainLoop = do
putStr "waiting for work ...\r"
hFlush stdout
-- Simulate work
-- Add your main loop logic here
threadDelay 1000000
mainLoop
handler :: AsyncException -> IO ()
handler UserInterrupt = putStrLn "\nCaught Ctrl-C. Exiting gracefully."

1
cabal.project Normal file
View File

@ -0,0 +1 @@
packages: *.cabal

View File

@ -1,127 +1,96 @@
cabal-version: 3.4
-- The cabal-version field refers to the version of the .cabal specification,
-- and can be different from the cabal-install (the tool) version and the
-- Cabal (the library) version you are using. As such, the Cabal (the library)
-- version used must be equal or greater than the version stated in this field.
-- Starting from the specification version 2.2, the cabal-version field must be
-- the first thing in the cabal file.
cabal-version: 1.12
-- Initial package description 'hagent' generated by
-- 'cabal init'. For further documentation, see:
-- http://haskell.org/cabal/users-guide/
-- This file has been generated from package.yaml by hpack version 0.38.0.
--
-- The name of the package.
-- see: https://github.com/sol/hpack
name: hagent
-- The package version.
-- See the Haskell package versioning policy (PVP) for standards
-- guiding when and how versions should be incremented.
-- https://pvp.haskell.org
-- PVP summary: +-+------- breaking API changes
-- | | +----- non-breaking API additions
-- | | | +--- code changes with no API change
version: 0.1.0.0
-- A short (one-line) description of the package.
-- synopsis:
-- A longer description of the package.
-- description:
-- The license under which the package is released.
license: AGPL-3.0-only
-- The file containing the license text.
license: AGPL-3.0
license-file: LICENSE
-- The package author(s).
author: Nicole Dresselhaus
-- An email address to which users can send suggestions, bug reports, and patches.
maintainer: nicole@dresselhaus.cloud
-- A copyright notice.
-- copyright:
build-type: Simple
-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
extra-doc-files: CHANGELOG.md
-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
-- extra-source-files:
common warnings
ghc-options: -Wall
library
-- Import common warning flags.
import: warnings
-- Modules exported by the library.
exposed-modules: MyLib
-- Modules included in this library but not exported.
-- other-modules:
-- LANGUAGE extensions used by modules in this package.
-- other-extensions:
-- Other library packages from which modules are imported.
build-depends: base ^>=4.20.0.0
-- Directories containing source files.
hs-source-dirs: src
-- Base language which the package is written in.
default-language: GHC2024
exposed-modules:
Agent.Server.Config
Agent.Tools.Tool
Agent.Tools.Time
other-modules:
Paths_hagent
build-depends:
base >=4.20.0 && <4.21,
bytestring,
data-default >=0.8.0 && <0.9,
directory >=1.3.8 && <1.4,
extra ==1.8.*,
filepath >=1.5.2 && <1.6,
ollama-haskell >=0.1.2 && <0.2,
template-haskell,
text,
yaml >=0.11.11 && <0.12
hs-source-dirs:
src
default-extensions:
OverloadedStrings
DerivingStrategies
DeriveAnyClass
DeriveGeneric
DerivingVia
GADTs
default-language: Haskell2010
executable hagent
-- Import common warning flags.
import: warnings
-- .hs or .lhs file containing the Main module.
main-is: Main.hs
-- Modules included in this executable, other than Main.
-- other-modules:
-- LANGUAGE extensions used by modules in this package.
-- other-extensions:
-- Other library packages from which modules are imported.
main-is: Main.hs
other-modules:
Paths_hagent
build-depends:
base ^>=4.20.0.0,
hagent
-- Directories containing source files.
hs-source-dirs: app
-- Base language which the package is written in.
default-language: GHC2024
base >=4.20.0 && <4.21,
bytestring,
data-default >=0.8.0 && <0.9,
directory >=1.3.8 && <1.4,
extra ==1.8.*,
filepath >=1.5.2 && <1.6,
hagent,
ollama-haskell >=0.1.2 && <0.2,
template-haskell,
text,
yaml >=0.11.11 && <0.12
hs-source-dirs:
app
default-extensions:
OverloadedStrings
DerivingStrategies
DeriveAnyClass
DeriveGeneric
DerivingVia
GADTs
default-language: Haskell2010
test-suite hagent-test
-- Import common warning flags.
import: warnings
-- Base language which the package is written in.
default-language: GHC2024
-- Modules included in this executable, other than Main.
-- other-modules:
-- LANGUAGE extensions used by modules in this package.
-- other-extensions:
-- The interface type and version of the test suite.
type: exitcode-stdio-1.0
-- Directories containing source files.
hs-source-dirs: test
-- The entrypoint to the test suite.
main-is: Main.hs
-- Test dependencies.
default-language: Haskell2010
type: exitcode-stdio-1.0
hs-source-dirs:
test
default-extensions:
OverloadedStrings
DerivingStrategies
DeriveAnyClass
DeriveGeneric
DerivingVia
GADTs
main-is: Main.hs
other-modules:
Paths_hagent
build-depends:
base ^>=4.20.0.0,
hagent
base >=4.20.0 && <4.21,
bytestring,
data-default >=0.8.0 && <0.9,
directory >=1.3.8 && <1.4,
extra ==1.8.*,
filepath >=1.5.2 && <1.6,
hagent,
ollama-haskell >=0.1.2 && <0.2,
text,
yaml >=0.11.11 && <0.12

43
package.yaml Normal file
View File

@ -0,0 +1,43 @@
name: hagent
version: '0.1.0.0'
author: Nicole Dresselhaus
maintainer: nicole@dresselhaus.cloud
license: AGPL-3.0
dependencies:
- base >= 4.20.0 && < 4.21
- data-default >= 0.8.0 && < 0.9
- directory >= 1.3.8 && < 1.4
- filepath >= 1.5.2 && < 1.6
- extra >= 1.8 && < 1.9
- ollama-haskell >= 0.1.2 && < 0.2
- yaml >= 0.11.11 && < 0.12
- text
- bytestring
default-extensions:
- OverloadedStrings
- DerivingStrategies
- DeriveAnyClass
- DeriveGeneric
- DerivingVia
- GADTs
library:
source-dirs: src
exposed-modules:
- Agent.Server.Config
- Agent.Tools.Tool
- Agent.Tools.Time
dependencies:
- template-haskell
executables:
hagent:
main: Main.hs
source-dirs: app
dependencies:
- hagent
- template-haskell
tests:
hagent-test:
main: Main.hs
source-dirs: test
dependencies:
- hagent

View File

@ -0,0 +1,92 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}
module Agent.Server.Config
( configPath,
loadConfig,
loadConfigOrDefault,
saveConfig,
Config (..),
OllamaConfig (..),
NoConfigFoundException,
configFile,
)
where
import Control.Exception (Exception (..))
import Control.Monad.Extra (whenMaybeM)
import Data.Default (Default (..))
import Data.Text (Text (..))
import Data.Yaml (FromJSON, ParseException (..), ToJSON, decodeFileEither, decodeFileThrow, encodeFile)
import GHC.Generics (Generic)
import System.Directory (XdgDirectory (..), createDirectoryIfMissing, doesFileExist, getXdgDirectory)
import System.FilePath ((</>))
data Config = Config
{ startOllama :: Bool,
ollama :: OllamaConfig
}
deriving stock (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
instance Default Config where
def =
Config
{ startOllama = False,
ollama = def
}
data OllamaConfig = OllamaConfig
{ host :: Text,
port :: Int,
cmdExtra :: Text
}
deriving stock (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
instance Default OllamaConfig where
def =
OllamaConfig
{ host = "localhost",
port = 11434,
cmdExtra = ""
}
data NoConfigFoundException = NotFound FilePath
deriving (Show)
instance Exception NoConfigFoundException
configPath :: FilePath
configPath = "hAgent"
configName :: FilePath
configName = "config.yaml"
configFile :: IO (Maybe FilePath)
configFile = do
filepath <- (getXdgDirectory XdgConfig (configPath </> configName))
whenMaybeM (doesFileExist filepath) (return filepath)
loadConfig :: IO (Either ParseException Config)
loadConfig = do
filepath <- (getXdgDirectory XdgConfig (configPath </> configName))
confFile <- configFile
case confFile of
Just f -> decodeFileEither f
Nothing -> return $ Left $ OtherParseException $ toException $ NotFound $ filepath
loadConfigOrDefault :: IO (Either ParseException Config)
loadConfigOrDefault = do
confFile <- configFile
case confFile of
Just f -> decodeFileEither f
Nothing -> return $ Right $ def
saveConfig :: Config -> IO ()
saveConfig conf = do
getXdgDirectory XdgConfig configPath >>= createDirectoryIfMissing True
confPath <- (getXdgDirectory XdgConfig (configPath </> configName))
encodeFile confPath conf

26
src/Agent/Tools/Time.hs Normal file
View File

@ -0,0 +1,26 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Agent.Tools.Time where
import Agent.Tools.Tool
import Agent.Tools.Tool (Tool)
import Control.Monad.IO.Class
import Data.Proxy
import Data.Text
data CurrentTimeTool
instance KnownTool CurrentTimeTool where
toolVal _ =
Tool
{ name = "current_time",
call = "",
description = "Get the current Time"
}
instance Dispatchable CurrentTimeTool () where
dispatch :: (MonadIO m) => Proxy t -> () -> m Text
dispatch Proxy () = return "Current Timed: 09.03.2025 14:38"
parseArgs _ _ = ()

46
src/Agent/Tools/Tool.hs Normal file
View File

@ -0,0 +1,46 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}
module Agent.Tools.Tool where
import Control.Monad.IO.Class
import qualified Data.Kind as DK (Type)
import Data.Proxy
import Data.Text
import Language.Haskell.TH
data Tool = Tool
{ name :: Text,
description :: Text,
call :: Text
}
deriving (Show)
toMarkdownDL :: Tool -> Text
toMarkdownDL t = "- " <> name t <> "(" <> call t <> ")\n: " <> description t
-- | This type represents unknown type-level tools.
data SomeTool = forall t. (KnownTool t) => SomeTool (Proxy t)
-- | This class gives the tool associated with a type-level tool.
-- There are instances of the class for every concrete tool included.
class KnownTool (t :: DK.Type) where
toolVal :: Proxy t -> Tool
class (KnownTool t) => Dispatchable t a where
dispatch :: (MonadIO m) => Proxy t -> a -> m Text
parseArgs :: Proxy t -> Text -> a
getToolListTH :: Q Exp
getToolListTH = do
ClassI _ instances <- reify ''Dispatchable
let toolExps =
[ [|toolVal (Proxy :: Proxy $(return t))|]
| InstanceD _ _ (AppT (AppT _ t) _) _ <-
instances
]
listE toolExps

View File

@ -1,4 +0,0 @@
module MyLib (someFunc) where
someFunc :: IO ()
someFunc = putStrLn "someFunc"