diff options
| author | Ben Sima <ben@bensima.com> | 2025-12-12 23:30:04 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-12-12 23:30:04 -0500 |
| commit | 817bdb1f33e9825946a2da2aa1ff8f91b6166366 (patch) | |
| tree | 32af363a03de72964e999ce437a7e01bfc80a85a /Omni/Agent/Tools | |
| parent | bfa50a5a755e13c0ee2394d89280092a639d8f0d (diff) | |
telegram bot: refactor + multimedia + reply support
Refactor Telegram.hs into submodules to reduce file size:
- Types.hs: data types, JSON parsing
- Media.hs: file downloads, image/voice analysis
- Reminders.hs: reminder loop, user chat persistence
Multimedia improvements:
- Vision uses third-person to avoid LLM confusion
- Better message framing for embedded descriptions
- Size validation (10MB images, 20MB voice)
- MIME type validation for voice messages
New features:
- Reply support: bot sees context when users reply
- Web search: default 5->10, max 10->20 results
- Guardrails: duplicate tool limit 3->10 for research
- Timezone: todos parse/display in Eastern time (ET)
Diffstat (limited to 'Omni/Agent/Tools')
| -rw-r--r-- | Omni/Agent/Tools/Todos.hs | 26 | ||||
| -rw-r--r-- | Omni/Agent/Tools/WebSearch.hs | 6 |
2 files changed, 21 insertions, 11 deletions
diff --git a/Omni/Agent/Tools/Todos.hs b/Omni/Agent/Tools/Todos.hs index 4c7d2be..2aacacc 100644 --- a/Omni/Agent/Tools/Todos.hs +++ b/Omni/Agent/Tools/Todos.hs @@ -45,8 +45,8 @@ import Alpha import Data.Aeson ((.!=), (.:), (.:?), (.=)) import qualified Data.Aeson as Aeson import qualified Data.Text as Text -import Data.Time (NominalDiffTime, UTCTime, addUTCTime, getCurrentTime) -import Data.Time.Format (defaultTimeLocale, parseTimeM) +import Data.Time (LocalTime, NominalDiffTime, TimeZone, UTCTime, addUTCTime, getCurrentTime, localTimeToUTC, minutesToTimeZone, utcToLocalTime) +import Data.Time.Format (defaultTimeLocale, formatTime, parseTimeM) import qualified Database.SQLite.Simple as SQL import qualified Omni.Agent.Engine as Engine import qualified Omni.Agent.Memory as Memory @@ -165,12 +165,18 @@ migrateTodosTable conn = do unless ("last_reminded_at" `elem` colNames) <| do SQL.execute_ conn "ALTER TABLE todos ADD COLUMN last_reminded_at TIMESTAMP" +easternTimeZone :: TimeZone +easternTimeZone = minutesToTimeZone (-300) + parseDueDate :: Text -> Maybe UTCTime parseDueDate txt = let s = Text.unpack txt - in parseTimeM True defaultTimeLocale "%Y-%m-%d %H:%M" s - <|> parseTimeM True defaultTimeLocale "%Y-%m-%d" s - <|> parseTimeM True defaultTimeLocale "%Y-%m-%dT%H:%M:%S" s + parseLocal :: Maybe LocalTime + parseLocal = + parseTimeM True defaultTimeLocale "%Y-%m-%d %H:%M" s + <|> parseTimeM True defaultTimeLocale "%Y-%m-%d" s + <|> parseTimeM True defaultTimeLocale "%Y-%m-%dT%H:%M:%S" s + in fmap (localTimeToUTC easternTimeZone) parseLocal <|> parseTimeM True defaultTimeLocale "%Y-%m-%dT%H:%M:%SZ" s createTodo :: Text -> Text -> Maybe Text -> IO Todo @@ -301,7 +307,7 @@ todoAddTool uid = "due_date" .= Aeson.object [ "type" .= ("string" :: Text), - "description" .= ("Optional due date: 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM'" :: Text) + "description" .= ("Optional due date in Eastern time: 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM'" :: Text) ] ], "required" .= (["title"] :: [Text]) @@ -316,7 +322,9 @@ executeTodoAdd uid v = Aeson.Success (args :: TodoAddArgs) -> do td <- createTodo uid (taTitle args) (taDueDate args) let dueDateMsg = case todoDueDate td of - Just d -> " (due: " <> tshow d <> ")" + Just d -> + let localTime = utcToLocalTime easternTimeZone d + in " (due: " <> Text.pack (formatTime defaultTimeLocale "%Y-%m-%d %H:%M ET" localTime) <> ")" Nothing -> "" pure ( Aeson.object @@ -392,7 +400,9 @@ formatTodosForLLM todos = formatTodo td = let status = if todoCompleted td then "[x]" else "[ ]" dueStr = case todoDueDate td of - Just d -> " (due: " <> Text.pack (show d) <> ")" + Just d -> + let localTime = utcToLocalTime easternTimeZone d + in " (due: " <> Text.pack (formatTime defaultTimeLocale "%Y-%m-%d %H:%M ET" localTime) <> ")" Nothing -> "" in status <> " " <> todoTitle td <> dueStr <> " (id: " <> tshow (todoId td) <> ")" diff --git a/Omni/Agent/Tools/WebSearch.hs b/Omni/Agent/Tools/WebSearch.hs index f7250b8..58c945c 100644 --- a/Omni/Agent/Tools/WebSearch.hs +++ b/Omni/Agent/Tools/WebSearch.hs @@ -172,7 +172,7 @@ webSearchTool apiKey = "limit" .= Aeson.object [ "type" .= ("integer" :: Text), - "description" .= ("Max results to return (default: 5, max: 10)" :: Text) + "description" .= ("Max results to return (default: 10, max: 20)" :: Text) ] ], "required" .= (["query"] :: [Text]) @@ -185,7 +185,7 @@ executeWebSearch apiKey v = case Aeson.fromJSON v of Aeson.Error e -> pure (Aeson.object ["error" .= Text.pack e]) Aeson.Success (args :: WebSearchArgs) -> do - let lim = min 10 (max 1 (wsLimit args)) + let lim = min 20 (max 1 (wsLimit args)) result <- kagiSearch apiKey (wsQuery args) lim case result of Left err -> @@ -209,4 +209,4 @@ instance Aeson.FromJSON WebSearchArgs where parseJSON = Aeson.withObject "WebSearchArgs" <| \v -> (WebSearchArgs </ (v Aeson..: "query")) - <*> (v Aeson..:? "limit" Aeson..!= 5) + <*> (v Aeson..:? "limit" Aeson..!= 10) |
