summaryrefslogtreecommitdiff
path: root/Omni/Agent/Tools.hs
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-12-19 23:13:46 -0500
committerBen Sima <ben@bensima.com>2025-12-19 23:13:46 -0500
commit7724895ef03013f27d43d90ea5acaf2e2e038b9f (patch)
treed24c0a1d5ac8bcc64aa185b7260c946d2bfb0d14 /Omni/Agent/Tools.hs
parentba466ffdba886d1a30b8fd7c6727ad69a6d40f2c (diff)
Omni/Agent: make token explosion impossible
Tools.hs: - run_bash now uses mkSuccess (applies truncation) - read_file requires line ranges for files >500 lines - read_file rejects ranges >400 lines Engine.hs: - Added engine-level truncateToolResult (10k char cap) - Fixed test detection: bash -> run_bash
Diffstat (limited to 'Omni/Agent/Tools.hs')
-rw-r--r--Omni/Agent/Tools.hs57
1 files changed, 42 insertions, 15 deletions
diff --git a/Omni/Agent/Tools.hs b/Omni/Agent/Tools.hs
index 0bd394f..5078b11 100644
--- a/Omni/Agent/Tools.hs
+++ b/Omni/Agent/Tools.hs
@@ -214,6 +214,14 @@ instance Aeson.FromJSON ToolResult where
maxOutputChars :: Int
maxOutputChars = 8000
+-- | Maximum lines per read_file call
+maxReadLinesPerCall :: Int
+maxReadLinesPerCall = 400
+
+-- | Files larger than this require explicit line ranges
+largeFileLineThreshold :: Int
+largeFileLineThreshold = 500
+
-- | Truncate output if too long, adding a notice
truncateOutput :: Text -> Text
truncateOutput output
@@ -296,12 +304,38 @@ executeReadFile v =
then do
content <- TextIO.readFile path
let allLines = Text.lines content
- startIdx = maybe 0 (\n -> n - 1) (readFileStartLine args)
- endIdx = maybe (length allLines) identity (readFileEndLine args)
- selectedLines = take (endIdx - startIdx) (drop startIdx allLines)
- numberedLines = zipWith formatLine [(startIdx + 1) ..] selectedLines
- result = Text.unlines numberedLines
- pure <| mkSuccess result
+ totalLines = length allLines
+ hasRange = isJust (readFileStartLine args) || isJust (readFileEndLine args)
+
+ if not hasRange && totalLines > largeFileLineThreshold
+ then
+ pure
+ <| mkError
+ ( "File is large ("
+ <> tshow totalLines
+ <> " lines). You must provide start_line and end_line, with at most "
+ <> tshow maxReadLinesPerCall
+ <> " lines per call."
+ )
+ else do
+ let startIdx = maybe 0 (\n -> n - 1) (readFileStartLine args)
+ endIdx = maybe (length allLines) identity (readFileEndLine args)
+ requestedLines = endIdx - startIdx
+ if requestedLines > maxReadLinesPerCall
+ then
+ pure
+ <| mkError
+ ( "Requested range is too large ("
+ <> tshow requestedLines
+ <> " lines). Max "
+ <> tshow maxReadLinesPerCall
+ <> " lines per read_file call."
+ )
+ else do
+ let selectedLines = take requestedLines (drop startIdx allLines)
+ numberedLines = zipWith formatLine [(startIdx + 1) ..] selectedLines
+ result = Text.unlines numberedLines
+ pure <| mkSuccess result
else pure <| mkError ("File not found: " <> readFilePath args)
where
formatLine :: Int -> Text -> Text
@@ -520,20 +554,13 @@ executeRunBash v =
(exitCode, stdoutStr, stderrStr) <- Process.readCreateProcessWithExitCode proc ""
let output = Text.pack stdoutStr <> Text.pack stderrStr
case exitCode of
- Exit.ExitSuccess ->
- pure
- <| Aeson.toJSON
- <| ToolResult
- { toolResultSuccess = True,
- toolResultOutput = output,
- toolResultError = Nothing
- }
+ Exit.ExitSuccess -> pure <| mkSuccess output
Exit.ExitFailure code ->
pure
<| Aeson.toJSON
<| ToolResult
{ toolResultSuccess = False,
- toolResultOutput = output,
+ toolResultOutput = truncateOutput output,
toolResultError = Just ("Exit code: " <> tshow code)
}