never executed always true always false
    1 module PureClaw.Agent.Loop
    2   ( -- * Agent loop
    3     runAgentLoop
    4   ) where
    5 
    6 import Control.Exception
    7 import Control.Monad
    8 import Data.Text (Text)
    9 import Data.Text qualified as T
   10 
   11 import PureClaw.Agent.Context
   12 import PureClaw.Core.Errors
   13 import PureClaw.Core.Types
   14 import PureClaw.Handles.Channel
   15 import PureClaw.Handles.Log
   16 import PureClaw.Providers.Class
   17 import PureClaw.Tools.Registry
   18 
   19 -- | Run the main agent loop. Reads messages from the channel, sends
   20 -- them to the provider (with tool definitions), handles tool call/result
   21 -- cycles, and writes responses back.
   22 --
   23 -- Exits cleanly on 'IOException' from the channel (e.g. EOF / Ctrl-D).
   24 -- Provider errors are logged and a 'PublicError' is sent to the channel.
   25 runAgentLoop :: Provider p => p -> ModelId -> ChannelHandle -> LogHandle -> Maybe Text -> ToolRegistry -> IO ()
   26 runAgentLoop provider model channel logger systemPrompt registry = do
   27   _lh_logInfo logger "Agent loop started"
   28   go (emptyContext systemPrompt)
   29   where
   30     tools = registryDefinitions registry
   31 
   32     go ctx = do
   33       receiveResult <- try @IOException (_ch_receive channel)
   34       case receiveResult of
   35         Left _ -> _lh_logInfo logger "Session ended"
   36         Right msg
   37           | T.null (T.strip (_im_content msg)) -> go ctx
   38           | otherwise -> do
   39               let userMsg = textMessage User (_im_content msg)
   40                   ctx' = addMessage userMsg ctx
   41               _lh_logDebug logger $
   42                 "Sending " <> T.pack (show (length (contextMessages ctx'))) <> " messages"
   43               handleCompletion ctx'
   44 
   45     handleCompletion ctx = do
   46       let req = CompletionRequest
   47             { _cr_model        = model
   48             , _cr_messages     = contextMessages ctx
   49             , _cr_systemPrompt = contextSystemPrompt ctx
   50             , _cr_maxTokens    = Just 4096
   51             , _cr_tools        = tools
   52             , _cr_toolChoice   = Nothing
   53             }
   54       providerResult <- try @SomeException (complete provider req)
   55       case providerResult of
   56         Left e -> do
   57           _lh_logError logger $ "Provider error: " <> T.pack (show e)
   58           _ch_sendError channel (TemporaryError "Something went wrong. Please try again.")
   59           go ctx
   60         Right response -> do
   61           let calls = toolUseCalls response
   62               text = responseText response
   63               ctx' = addMessage (Message Assistant (_crsp_content response)) ctx
   64           -- Send any text to the channel
   65           unless (T.null (T.strip text)) $
   66             _ch_send channel (OutgoingMessage text)
   67           -- If there are tool calls, execute them and continue
   68           if null calls
   69             then go ctx'
   70             else do
   71               results <- mapM executeCall calls
   72               let resultMsg = toolResultMessage results
   73                   ctx'' = addMessage resultMsg ctx'
   74               _lh_logDebug logger $
   75                 "Executed " <> T.pack (show (length results)) <> " tool calls, continuing"
   76               handleCompletion ctx''
   77 
   78     executeCall (callId, name, input) = do
   79       _lh_logInfo logger $ "Tool call: " <> name
   80       result <- executeTool registry name input
   81       case result of
   82         Nothing -> do
   83           _lh_logWarn logger $ "Unknown tool: " <> name
   84           pure (callId, "Unknown tool: " <> name, True)
   85         Just (output, isErr) -> do
   86           when isErr $ _lh_logWarn logger $ "Tool error in " <> name <> ": " <> output
   87           pure (callId, output, isErr)