summaryrefslogtreecommitdiff
path: root/Omni/Agent
diff options
context:
space:
mode:
Diffstat (limited to 'Omni/Agent')
-rw-r--r--Omni/Agent/Engine.hs19
-rw-r--r--Omni/Agent/Tools.hs57
2 files changed, 59 insertions, 17 deletions
diff --git a/Omni/Agent/Engine.hs b/Omni/Agent/Engine.hs
index 0dc7c50..343ccc3 100644
--- a/Omni/Agent/Engine.hs
+++ b/Omni/Agent/Engine.hs
@@ -799,8 +799,9 @@ executeToolCallsWithTracking engineCfg toolMap tcs initialTestFailures initialEd
resultValue <- toolExecute tool args
endTime <- Time.getCurrentTime
let durationMs = round (Time.diffUTCTime endTime startTime * 1000)
- resultText = TE.decodeUtf8 (BL.toStrict (Aeson.encode resultValue))
- isTestCall = name == "bash" && ("bild --test" `Text.isInfixOf` argsText || "bild -t" `Text.isInfixOf` argsText)
+ rawResultText = TE.decodeUtf8 (BL.toStrict (Aeson.encode resultValue))
+ resultText = truncateToolResult rawResultText
+ isTestCall = name == "run_bash" && ("bild --test" `Text.isInfixOf` argsText || "bild -t" `Text.isInfixOf` argsText)
isTestFailure = isTestCall && isFailureResult resultValue
testDelta = if isTestFailure then 1 else 0
isEditFailure = name == "edit_file" && isOldStrNotFoundError resultValue
@@ -830,6 +831,20 @@ executeToolCallsWithTracking engineCfg toolMap tcs initialTestFailures initialEd
_ -> False
isOldStrNotFoundError _ = False
+-- | Maximum characters for any tool result (engine-level safety net)
+maxToolResultChars :: Int
+maxToolResultChars = 10000
+
+-- | Truncate tool result if too long (engine-level safety net)
+truncateToolResult :: Text -> Text
+truncateToolResult t
+ | Text.length t <= maxToolResultChars = t
+ | otherwise =
+ Text.take maxToolResultChars t
+ <> "\n\n[TOOL RESULT TRUNCATED by engine - "
+ <> tshow (Text.length t - maxToolResultChars)
+ <> " chars omitted]"
+
-- | Estimate cost in cents from token count.
-- Uses blended input/output rates (roughly 2:1 output:input ratio).
-- Prices as of Dec 2024 from OpenRouter.
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)
}