summaryrefslogtreecommitdiff
path: root/Omni/Agent/Log.hs
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-11-22 16:31:45 -0500
committerBen Sima <ben@bensima.com>2025-11-22 16:31:45 -0500
commitbb15513a94140c22aa3aea510314f60c94df4d97 (patch)
treee9ef6649236eadeb58f6dc445aba302e72edfb9c /Omni/Agent/Log.hs
parent6f4b2c97a24967508f3970b46999052fd1f44e67 (diff)
feat: implement t-1o2bxd11zv9
The task to fix missing Time, Thread, and Credits in the Agent Log has been completed. **Changes Implemented:** 1. **`Omni/Agent/Log.hs`**: * Added `Data.Aeson` and `Data.ByteString` imports for JSON parsing. * Updated `Status` data type to include `statusThread`. * Implemented `LogEntry` data type and `FromJSON` instance to match the `amp` log format. * Added `processLogLine` function to parse JSON log lines and update the global status. * Updated `render` function to display the Thread ID. * Added logic to extract and format `Time` and `Credits` from log entries. 2. **`Omni/Agent/Worker.hs`**: * Added a log monitoring thread using `forkIO` in `runAmp`. * Implemented `monitorLog` to tail the `_/llm/amp.log` file and pass lines to `AgentLog.processLogLine`. * Added `waitForFile` to ensure the log monitor waits for the log file to be created. **Verification:** * Verified that both `Omni/Agent/Log.hs` and `Omni/Agent/Worker.hs` compile successfully using `bild` (ignoring the expected "no main" error for library modules). * Ran `lint` on both files with no errors. The agent status bar should now correctly display the Thread ID, elapsed/current Time, and Credits usage as parsed from the `amp` logs.
Diffstat (limited to 'Omni/Agent/Log.hs')
-rw-r--r--Omni/Agent/Log.hs56
1 files changed, 55 insertions, 1 deletions
diff --git a/Omni/Agent/Log.hs b/Omni/Agent/Log.hs
index afaf1da..0672170 100644
--- a/Omni/Agent/Log.hs
+++ b/Omni/Agent/Log.hs
@@ -6,7 +6,12 @@
module Omni.Agent.Log where
import Alpha
+import qualified Data.Aeson as Aeson
+import Data.Aeson ((.:), (.:?))
+import qualified Data.ByteString.Lazy as BL
import Data.IORef (IORef, modifyIORef', newIORef, readIORef, writeIORef)
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as TE
import qualified Data.Text.IO as TIO
import qualified System.Console.ANSI as ANSI
import qualified System.IO as IO
@@ -16,6 +21,7 @@ import System.IO.Unsafe (unsafePerformIO)
data Status = Status
{ statusWorker :: Text,
statusTask :: Maybe Text,
+ statusThread :: Maybe Text,
statusFiles :: Int,
statusCredits :: Double,
statusTime :: Text, -- formatted time string
@@ -28,6 +34,7 @@ emptyStatus workerName =
Status
{ statusWorker = workerName,
statusTask = Nothing,
+ statusThread = Nothing,
statusFiles = 0,
statusCredits = 0.0,
statusTime = "00:00",
@@ -81,13 +88,16 @@ render = do
Status {..} <- readIORef currentStatus
-- Line 1: Meta
- -- [Worker: name] Task: t-123 | Files: 3 | Credits: $0.45 | Time: 05:23
+ -- [Worker: name] Task: t-123 | Thread: T-abc | Files: 3 | Credits: $0.45 | Time: 05:23
let taskStr = maybe "None" identity statusTask
+ threadStr = maybe "None" identity statusThread
meta =
"[Worker: "
<> statusWorker
<> "] Task: "
<> taskStr
+ <> " | Thread: "
+ <> threadStr
<> " | Files: "
<> tshow statusFiles
<> " | Credits: $"
@@ -109,3 +119,47 @@ render = do
-- Return cursor to line 1
ANSI.hCursorUp IO.stderr 1
IO.hFlush IO.stderr
+
+-- | Log Entry from JSON
+data LogEntry = LogEntry
+ { leMessage :: Text,
+ leThreadId :: Maybe Text,
+ leCredits :: Maybe Double,
+ leTotalCredits :: Maybe Double,
+ leTimestamp :: Maybe Text
+ }
+ deriving (Show, Eq)
+
+instance Aeson.FromJSON LogEntry where
+ parseJSON = Aeson.withObject "LogEntry" $ \v ->
+ LogEntry
+ <$> v .: "message"
+ <*> v .:? "threadId"
+ <*> v .:? "credits"
+ <*> v .:? "totalCredits"
+ <*> v .:? "timestamp"
+
+-- | Parse a log line and update status
+processLogLine :: Text -> IO ()
+processLogLine line = do
+ let bs = BL.fromStrict $ TE.encodeUtf8 line
+ case Aeson.decode bs of
+ Just entry -> update (updateFromEntry entry)
+ Nothing -> pure () -- Ignore invalid JSON
+
+updateFromEntry :: LogEntry -> Status -> Status
+updateFromEntry LogEntry {..} s =
+ s
+ { statusThread = leThreadId <|> statusThread s,
+ statusCredits = fromMaybe (statusCredits s) (leTotalCredits <|> leCredits),
+ statusTime = maybe (statusTime s) formatTime leTimestamp
+ }
+
+formatTime :: Text -> Text
+formatTime ts =
+ -- "2025-11-22T21:24:02.512Z" -> "21:24"
+ case Text.splitOn "T" ts of
+ [_, time] -> case Text.splitOn ":" time of
+ (h : m : _) -> h <> ":" <> m
+ _ -> ts
+ _ -> ts