summaryrefslogtreecommitdiff
path: root/Omni/Agent
diff options
context:
space:
mode:
Diffstat (limited to 'Omni/Agent')
-rw-r--r--Omni/Agent/Tools.hs84
-rw-r--r--Omni/Agent/Worker.hs10
2 files changed, 87 insertions, 7 deletions
diff --git a/Omni/Agent/Tools.hs b/Omni/Agent/Tools.hs
index 9664c76..22cc8a1 100644
--- a/Omni/Agent/Tools.hs
+++ b/Omni/Agent/Tools.hs
@@ -22,12 +22,14 @@ module Omni.Agent.Tools
editFileTool,
runBashTool,
searchCodebaseTool,
+ searchAndReadTool,
allTools,
ReadFileArgs (..),
WriteFileArgs (..),
EditFileArgs (..),
RunBashArgs (..),
SearchCodebaseArgs (..),
+ SearchAndReadArgs (..),
ToolResult (..),
main,
test,
@@ -78,8 +80,8 @@ test =
case schema of
Aeson.Object _ -> pure ()
_ -> Test.assertFailure "Schema should be an object",
- Test.unit "allTools contains 5 tools" <| do
- length allTools Test.@=? 5,
+ Test.unit "allTools contains 6 tools" <| do
+ length allTools Test.@=? 6,
Test.unit "ReadFileArgs parses correctly" <| do
let json = Aeson.object ["path" .= ("test.txt" :: Text)]
case Aeson.fromJSON json of
@@ -220,7 +222,8 @@ allTools =
writeFileTool,
editFileTool,
runBashTool,
- searchCodebaseTool
+ searchCodebaseTool,
+ searchAndReadTool
]
data ReadFileArgs = ReadFileArgs
@@ -602,3 +605,78 @@ executeSearchCodebase v =
pure <| mkSuccess "No matches found"
Exit.ExitFailure code ->
pure <| mkError ("ripgrep failed with code " <> tshow code <> ": " <> Text.pack stderrStr)
+
+data SearchAndReadArgs = SearchAndReadArgs
+ { sarPattern :: Text,
+ sarPath :: Maybe Text,
+ sarContextLines :: Maybe Int
+ }
+ deriving (Show, Eq, Generic)
+
+instance Aeson.FromJSON SearchAndReadArgs where
+ parseJSON =
+ Aeson.withObject "SearchAndReadArgs" <| \v ->
+ (SearchAndReadArgs </ (v .: "pattern"))
+ <*> (v .:? "path")
+ <*> (v .:? "context_lines")
+
+searchAndReadTool :: Engine.Tool
+searchAndReadTool =
+ Engine.Tool
+ { Engine.toolName = "search_and_read",
+ Engine.toolDescription =
+ "Search for a pattern, then read the matching lines with context. "
+ <> "More efficient than search + read separately - returns file content "
+ <> "around each match. Use this to find and understand code in one step.",
+ Engine.toolJsonSchema =
+ Aeson.object
+ [ "type" .= ("object" :: Text),
+ "properties"
+ .= Aeson.object
+ [ "pattern"
+ .= Aeson.object
+ [ "type" .= ("string" :: Text),
+ "description" .= ("Regex pattern to search for" :: Text)
+ ],
+ "path"
+ .= Aeson.object
+ [ "type" .= ("string" :: Text),
+ "description" .= ("Optional: directory or file to search in" :: Text)
+ ],
+ "context_lines"
+ .= Aeson.object
+ [ "type" .= ("integer" :: Text),
+ "description" .= ("Lines of context around each match (default: 10)" :: Text)
+ ]
+ ],
+ "required" .= (["pattern"] :: [Text])
+ ],
+ Engine.toolExecute = executeSearchAndRead
+ }
+
+executeSearchAndRead :: Aeson.Value -> IO Aeson.Value
+executeSearchAndRead v =
+ case Aeson.fromJSON v of
+ Aeson.Error e -> pure <| mkError (Text.pack e)
+ Aeson.Success args -> do
+ let pat = Text.unpack (sarPattern args)
+ ctx = fromMaybe 10 (sarContextLines args)
+ pathArg = maybe ["."] (\p -> [Text.unpack p]) (sarPath args)
+ rgArgs =
+ [ "--line-number",
+ "--no-heading",
+ "--context=" <> show ctx,
+ "--max-count=20",
+ "--ignore-case",
+ pat
+ ]
+ <> pathArg
+ proc = Process.proc "rg" rgArgs
+ (exitCode, stdoutStr, stderrStr) <- Process.readCreateProcessWithExitCode proc ""
+ case exitCode of
+ Exit.ExitSuccess ->
+ pure <| mkSuccess (Text.pack stdoutStr)
+ Exit.ExitFailure 1 ->
+ pure <| mkSuccess "No matches found"
+ Exit.ExitFailure code ->
+ pure <| mkError ("ripgrep failed: " <> tshow code <> ": " <> Text.pack stderrStr)
diff --git a/Omni/Agent/Worker.hs b/Omni/Agent/Worker.hs
index 07293c5..45caf9b 100644
--- a/Omni/Agent/Worker.hs
+++ b/Omni/Agent/Worker.hs
@@ -399,12 +399,14 @@ buildBasePrompt task ns repo =
<> "- Do not re-run passing tests\n"
<> "- Do not test files individually when namespace test covers them\n"
<> "- Aim to complete the task in under 50 tool calls\n\n"
- <> "LARGE FILE HANDLING:\n"
- <> "- When reading large files (>500 lines), use line ranges to read only relevant sections\n"
+ <> "EFFICIENT FILE READING:\n"
+ <> "- PREFER search_and_read over separate search + read_file calls\n"
+ <> "- search_and_read finds code AND returns context around matches in one call\n"
+ <> "- Only use read_file with line ranges (start_line/end_line) for targeted reads\n"
+ <> "- NEVER read entire large files - always search first, then read specific sections\n"
<> "- For edit_file, use minimal unique context - just enough lines to match uniquely\n"
<> "- If edit_file fails with 'old_str not found', re-read the exact lines you need to edit\n"
- <> "- After 2-3 failed edits on the same file, STOP and reconsider your approach\n"
- <> "- Very large files (>2000 lines) may need refactoring - note this for human review\n\n"
+ <> "- After 2-3 failed edits on the same file, STOP and reconsider your approach\n\n"
<> "Context:\n"
<> "- Working directory: "
<> Text.pack repo