Repository navigation
ADFA-6268 | Add read_app_logs and read_ide_logs agent tools #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
d85ceda
feat(ai-core): add read_app_logs and read_ide_logs agent tools (ADFA-…
jatezzz 54b818b
fix(ai-core): address review on log tools (ADFA-6268)
jatezzz 61bb72c
fix(ai-core): address second review on log tools
jatezzz 78ecd26
fix(ai-core): return small log reads whole, anchor on crash when it fits
jatezzz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
...src/main/kotlin/com/itsaky/androidide/plugins/aicore/tool/handlers/LogWindowCalculator.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| package com.itsaky.androidide.plugins.aicore.tool.handlers | ||
|
|
||
| import com.itsaky.androidide.plugins.aicore.tool.AgentLoop | ||
| import com.itsaky.androidide.plugins.services.LogEntry | ||
| import com.itsaky.androidide.plugins.services.LogLevel | ||
|
|
||
| /** | ||
| * Selects the slice of a log the model needs, in whole lines and within [MAX_OUTPUT_CHARS] | ||
| * including the truncation markers. Only the kept lines are ever joined into a string. | ||
| */ | ||
| internal object LogWindowCalculator { | ||
| /** Maximum characters of log handed to the model: AgentLoop's cap, less room for the message. */ | ||
| const val MAX_OUTPUT_CHARS = AgentLoop.DEFAULT_TOOL_OUTPUT_CHAR_LIMIT - 100 | ||
|
|
||
| /** Markers of an app crash; the anchor prefers these over a later error when the crash fits. */ | ||
| private val CRASH_MARKERS = listOf("FATAL EXCEPTION", "Fatal signal") | ||
|
|
||
| /** | ||
| * A read that fits is returned whole. Logs span runs, so otherwise the window starts at the newest | ||
| * crash if it and all after it fit, else at whichever is newer, that crash or the start of the | ||
| * newest run of error lines. With neither, or a log of nothing but errors, it keeps the newest lines. | ||
| */ | ||
| fun windowFor(entries: List<LogEntry>, hostTruncated: Boolean): OutputWindow { | ||
| if (!hostTruncated && fits(entries, MAX_OUTPUT_CHARS)) return OutputWindow(joined(entries), false) | ||
| val crash = entries.indexOfLast { isCrashLine(it) }.takeIf { it >= 0 } | ||
| val markedRoom = MAX_OUTPUT_CHARS - TRUNCATION_MARKER.length - 1 | ||
| val anchor = crash?.takeIf { fits(entries.subList(it, entries.size), markedRoom) } | ||
| ?: listOfNotNull(crash, newestErrorRunStart(entries)).maxOrNull() | ||
| val text = if (anchor != null) { | ||
| fromAnchor(entries.subList(anchor, entries.size), droppedBefore = hostTruncated || anchor > 0) | ||
|
jatezzz marked this conversation as resolved.
|
||
| } else { | ||
| tail(entries) | ||
| } | ||
| return OutputWindow(text = text, anchoredOnError = anchor != null) | ||
| } | ||
|
|
||
| /** [entries] whole if they fit, else their head and their newest lines, half the room each. */ | ||
| private fun fromAnchor(entries: List<LogEntry>, droppedBefore: Boolean): String { | ||
| val prefix = if (droppedBefore) "$TRUNCATION_MARKER\n" else "" | ||
| val room = MAX_OUTPUT_CHARS - prefix.length | ||
| if (fits(entries, room)) return prefix + joined(entries) | ||
|
|
||
| val separator = "\n$TRUNCATION_MARKER\n" | ||
| val half = (room - separator.length) / 2 | ||
| return prefix + headBody(entries, half) + separator + tailBody(entries, half) | ||
| } | ||
|
|
||
| /** The newest lines of [entries] that fit, marking what was dropped before them. */ | ||
| private fun tail(entries: List<LogEntry>): String { | ||
| val prefix = "$TRUNCATION_MARKER\n" | ||
| return prefix + tailBody(entries, MAX_OUTPUT_CHARS - prefix.length) | ||
| } | ||
|
|
||
| /** The oldest whole lines of [entries] that fit in [room], else the start of the first. */ | ||
| private fun headBody(entries: List<LogEntry>, room: Int): String { | ||
| val kept = fitCount(entries, room) | ||
| return if (kept > 0) joined(entries.subList(0, kept)) else keepStart(entries.first().text, room) | ||
| } | ||
|
|
||
| /** The newest whole lines of [entries] that fit in [room], else the end of the last. */ | ||
| private fun tailBody(entries: List<LogEntry>, room: Int): String { | ||
| val kept = fitCount(entries.asReversed(), room) | ||
| return if (kept > 0) { | ||
| joined(entries.subList(entries.size - kept, entries.size)) | ||
| } else { | ||
| keepEnd(entries.last().text, room) | ||
| } | ||
| } | ||
|
|
||
| /** How many of [entries], taken in order, fit in [room] characters once joined by newlines. */ | ||
| private fun fitCount(entries: List<LogEntry>, room: Int): Int { | ||
| var used = -1 // the first line needs no separator | ||
| var count = 0 | ||
| for (entry in entries) { | ||
| used += entry.text.length + 1 | ||
| if (used > room) break | ||
| count++ | ||
| } | ||
| return count | ||
| } | ||
|
|
||
| private fun fits(entries: List<LogEntry>, room: Int): Boolean = fitCount(entries, room) == entries.size | ||
|
|
||
| private fun joined(entries: List<LogEntry>): String = entries.joinToString("\n") { it.text } | ||
|
|
||
| /** The start of one oversized line, never splitting a surrogate pair. */ | ||
| private fun keepStart(text: String, room: Int): String { | ||
| val end = if (text[room - 1].isHighSurrogate()) room - 1 else room | ||
| return text.substring(0, end) | ||
| } | ||
|
|
||
| /** The end of one oversized line, never splitting a surrogate pair. */ | ||
| private fun keepEnd(text: String, room: Int): String { | ||
| val start = text.length - room | ||
| return text.substring(if (text[start].isLowSurrogate()) start + 1 else start) | ||
| } | ||
|
|
||
| /** | ||
| * The first line of the newest contiguous run of ERROR lines, so its trace keeps its head; null | ||
| * when every line is an ERROR, where a head would keep the oldest errors. | ||
| */ | ||
| private fun newestErrorRunStart(entries: List<LogEntry>): Int? { | ||
| var start = entries.indexOfLast { it.level == LogLevel.ERROR }.takeIf { it >= 0 } ?: return null | ||
| while (start > 0 && entries[start - 1].level == LogLevel.ERROR) start-- | ||
| return start.takeUnless { it == 0 && entries.last().level == LogLevel.ERROR } | ||
| } | ||
|
|
||
| private fun isCrashLine(entry: LogEntry): Boolean = | ||
| CRASH_MARKERS.any { entry.text.contains(it, ignoreCase = true) } | ||
| } | ||
10 changes: 10 additions & 0 deletions
10
...I-Core/src/main/kotlin/com/itsaky/androidide/plugins/aicore/tool/handlers/OutputWindow.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.itsaky.androidide.plugins.aicore.tool.handlers | ||
|
|
||
| /** Marks where a window dropped text, shared so the build and log windows read the same. */ | ||
| internal const val TRUNCATION_MARKER = "...[truncated]..." | ||
|
|
||
| /** The slice of a build or app/IDE log handed to the model, and whether it starts at an error. */ | ||
| internal data class OutputWindow( | ||
| val text: String, | ||
| val anchoredOnError: Boolean, | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.