feat: add read_terminal and computer_use custom parsers (#409)
* feat: add read_terminal and computer_use custom parsers * fix: add missing else branch to if expression in computer_use multimodal text extraction --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
a0954e2654
commit
a998b98aa2
1 file changed
+281
@@ -1171,6 +1171,287 @@ fun parseToolOutput(
|
||||
)
|
||||
}
|
||||
|
||||
// ── Image Generate-specific formatting ──
|
||||
if (resolvedToolName == "image_generate") {
|
||||
val success = dataSource.get("success")?.takeIf { !it.isJsonNull }?.asBoolean ?: true
|
||||
val errorMsg = dataSource.get("error")?.takeIf { !it.isJsonNull }?.asString
|
||||
val imageUrl = dataSource.get("image")?.takeIf { !it.isJsonNull }?.asString
|
||||
val argsImageUrl = argsObj?.get("image_url")?.takeIf { !it.isJsonNull }?.asString
|
||||
val modality = dataSource.get("modality")?.takeIf { !it.isJsonNull }?.asString
|
||||
val promptUrl = argsObj?.get("prompt")?.takeIf { !it.isJsonNull }?.asString
|
||||
|
||||
val summaryText =
|
||||
buildString {
|
||||
append("🎨 ")
|
||||
if (errorMsg != null) {
|
||||
append("❌ $errorMsg")
|
||||
} else {
|
||||
append((imageUrl ?: argsImageUrl ?: "").take(60))
|
||||
if (modality != null) append(" ($modality)")
|
||||
}
|
||||
}
|
||||
val mainOutput =
|
||||
buildString {
|
||||
if (errorMsg != null) {
|
||||
append("❌ $errorMsg")
|
||||
if (imageUrl != null) append("\n🔗 $imageUrl")
|
||||
} else if (imageUrl != null) {
|
||||
append("🖼️ ")
|
||||
if (promptUrl != null) append("$promptUrl\n")
|
||||
append("\n🔗 $imageUrl")
|
||||
} else {
|
||||
append("✅ Generated")
|
||||
}
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
mainOutput = mainOutput,
|
||||
durationSec = duration,
|
||||
isRunning = isRunning,
|
||||
)
|
||||
}
|
||||
|
||||
// ── Project List-specific formatting ──
|
||||
if (resolvedToolName == "project_list") {
|
||||
val activeId = dataSource.get("active_id")?.takeIf { !it.isJsonNull }?.asString
|
||||
val projectsArr = dataSource.get("projects")?.takeIf { !it.isJsonNull && it.isJsonArray }?.asJsonArray
|
||||
|
||||
val summaryText = "📂 ${projectsArr?.size() ?: "?"} projects${activeId?.let { " (1 active)" } ?: ""}"
|
||||
val mainOutput =
|
||||
if (projectsArr != null) {
|
||||
projectsArr.mapNotNull { el ->
|
||||
if (!el.isJsonObject) return@mapNotNull null
|
||||
val p = el.asJsonObject
|
||||
val id = p.get("id")?.takeIf { !it.isJsonNull }?.asString ?: ""
|
||||
val name = p.get("name")?.takeIf { !it.isJsonNull }?.asString ?: ""
|
||||
val slug = p.get("slug")?.takeIf { !it.isJsonNull }?.asString
|
||||
val path = p.get("primary_path")?.takeIf { !it.isJsonNull }?.asString
|
||||
val isActive = p.get("active")?.takeIf { !it.isJsonNull }?.asBoolean ?: false
|
||||
val lines = mutableListOf("${if (isActive) "⭐ " else "📁 "}$name")
|
||||
if (slug != null) lines.add(" 🏷️ $slug")
|
||||
if (path != null) lines.add(" 📍 $path")
|
||||
if (isActive) lines.add(" ✅ ACTIVE PROJECT")
|
||||
lines.joinToString("\n")
|
||||
}.joinToString("\n\n")
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
mainOutput = mainOutput,
|
||||
durationSec = duration,
|
||||
isRunning = isRunning,
|
||||
)
|
||||
}
|
||||
|
||||
// ── Project Create/Switch-specific formatting ──
|
||||
if (resolvedToolName == "project_create" || resolvedToolName == "project_switch") {
|
||||
val success = dataSource.get("success")?.takeIf { !it.isJsonNull }?.asBoolean ?: true
|
||||
val errorMsg = dataSource.get("error")?.takeIf { !it.isJsonNull }?.asString
|
||||
val projName = dataSource.get("name")?.takeIf { !it.isJsonNull }?.asString
|
||||
val projSlug = dataSource.get("slug")?.takeIf { !it.isJsonNull }?.asString
|
||||
val projPath = dataSource.get("primary_path")?.takeIf { !it.isJsonNull }?.asString
|
||||
val actionLabel = if (resolvedToolName == "project_create") "Created" else "Switched to"
|
||||
|
||||
val summaryText = "📂 $actionLabel${projName?.let { " $it" } ?: ""}"
|
||||
val mainOutput =
|
||||
buildString {
|
||||
if (errorMsg != null) {
|
||||
append("❌ $errorMsg")
|
||||
} else {
|
||||
append("✅ $actionLabel")
|
||||
if (projName != null) append(": $projName")
|
||||
if (projSlug != null) append("\n 🏷️ $projSlug")
|
||||
if (projPath != null) append("\n 📍 $projPath")
|
||||
}
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
mainOutput = mainOutput,
|
||||
durationSec = duration,
|
||||
isRunning = isRunning,
|
||||
)
|
||||
}
|
||||
|
||||
// ── Read Terminal-specific formatting ──
|
||||
if (resolvedToolName == "read_terminal") {
|
||||
val totalLines = dataSource.get("total_lines")?.takeIf { !it.isJsonNull }?.asInt ?: 0
|
||||
val startLine = dataSource.get("start")?.takeIf { !it.isJsonNull }?.asInt ?: 0
|
||||
val endLine = dataSource.get("end")?.takeIf { !it.isJsonNull }?.asInt ?: 0
|
||||
val cursorRow = dataSource.get("cursor_row")?.takeIf { !it.isJsonNull }?.asInt
|
||||
val text = dataSource.get("text")?.takeIf { !it.isJsonNull }?.asString
|
||||
val errorMsg = dataSource.get("error")?.takeIf { !it.isJsonNull }?.asString
|
||||
|
||||
val summaryText =
|
||||
buildString {
|
||||
append("🖥️ ")
|
||||
if (errorMsg != null) {
|
||||
append("❌ $errorMsg")
|
||||
} else if (text != null) {
|
||||
append("Lines $startLine-$endLine of $totalLines")
|
||||
if (cursorRow != null) append(", cursor at row $cursorRow")
|
||||
} else {
|
||||
append("Terminal output")
|
||||
}
|
||||
}
|
||||
val mainOutput =
|
||||
buildString {
|
||||
if (errorMsg != null) {
|
||||
append("❌ $errorMsg")
|
||||
} else if (text != null) {
|
||||
append("━━━ Terminal ━━━ ($startLine:$endLine / $totalLines)")
|
||||
if (cursorRow != null) append(" │ cursor row $cursorRow")
|
||||
append("\n$text")
|
||||
} else {
|
||||
append("🖥️ No terminal output")
|
||||
}
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
mainOutput = mainOutput,
|
||||
durationSec = duration,
|
||||
isRunning = isRunning,
|
||||
)
|
||||
}
|
||||
|
||||
// ── Computer Use-specific formatting ──
|
||||
if (resolvedToolName == "computer_use") {
|
||||
val action = dataSource.get("action")?.takeIf { !it.isJsonNull }?.asString
|
||||
val ok = dataSource.get("ok")?.takeIf { !it.isJsonNull }?.asBoolean
|
||||
val message = dataSource.get("message")?.takeIf { !it.isJsonNull }?.asString
|
||||
val errorMsg = dataSource.get("error")?.takeIf { !it.isJsonNull }?.asString
|
||||
val isMultimodal = dataSource.get("_multimodal")?.takeIf { !it.isJsonNull }?.asBoolean ?: false
|
||||
val mode = dataSource.get("mode")?.takeIf { !it.isJsonNull }?.asString
|
||||
val elementsArr = dataSource.get("elements")?.takeIf { !it.isJsonNull && it.isJsonArray }?.asJsonArray
|
||||
val appsArr = dataSource.get("apps")?.takeIf { !it.isJsonNull && it.isJsonArray }?.asJsonArray
|
||||
val summaryText = dataSource.get("text_summary")?.takeIf { !it.isJsonNull }?.asString
|
||||
val textSummary = dataSource.get("summary")?.takeIf { !it.isJsonNull }?.asString
|
||||
val visionAnalysis = dataSource.get("vision_analysis")?.takeIf { !it.isJsonNull }?.asString
|
||||
val totalElements = dataSource.get("total_elements")?.takeIf { !it.isJsonNull }?.asInt
|
||||
val width = dataSource.get("width")?.takeIf { !it.isJsonNull }?.asInt
|
||||
val height = dataSource.get("height")?.takeIf { !it.isJsonNull }?.asInt
|
||||
|
||||
// Try to extract text from multimodal content array
|
||||
val multimodalText =
|
||||
if (isMultimodal) {
|
||||
val contentArr =
|
||||
dataSource.get("content")?.takeIf { !it.isJsonNull && it.isJsonArray }?.asJsonArray
|
||||
contentArr?.mapNotNull { el ->
|
||||
if (el.isJsonObject) {
|
||||
val eObj = el.asJsonObject
|
||||
if (eObj.get("type")?.takeIf { !it.isJsonNull }?.asString == "text") {
|
||||
eObj.get("text")?.takeIf { !it.isJsonNull }?.asString
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}?.joinToString("\n")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val summaryLine =
|
||||
buildString {
|
||||
append("🖥️ ")
|
||||
if (errorMsg != null) {
|
||||
append("❌ $errorMsg")
|
||||
} else if (appsArr != null) {
|
||||
append("${appsArr.size()} apps")
|
||||
} else if (elementsArr != null) {
|
||||
append("${elementsArr.size()} elements${mode?.let { " ($it)" } ?: ""}")
|
||||
if (width != null && height != null) append(" ${width}x$height")
|
||||
} else if (action != null) {
|
||||
append("$action${ok?.let { if (it) " ✓" else " ✗" } ?: ""}")
|
||||
} else {
|
||||
append("Computer use")
|
||||
}
|
||||
}
|
||||
val mainBody =
|
||||
buildString {
|
||||
if (errorMsg != null) {
|
||||
append("❌ $errorMsg")
|
||||
} else if (multimodalText != null) {
|
||||
append(multimodalText)
|
||||
if (textSummary != null && multimodalText != textSummary) {
|
||||
append("\n\n$textSummary")
|
||||
}
|
||||
} else if (summaryText != null) {
|
||||
append(summaryText)
|
||||
} else if (textSummary != null) {
|
||||
append(textSummary)
|
||||
} else if (appsArr != null) {
|
||||
appsArr.forEachIndexed { idx, el ->
|
||||
if (el.isJsonObject) {
|
||||
val name = el.asJsonObject.get("name")?.takeIf { !it.isJsonNull }?.asString ?: "?"
|
||||
val pid = el.asJsonObject.get("pid")?.takeIf { !it.isJsonNull }?.asInt
|
||||
append("${idx + 1}. $name")
|
||||
if (pid != null) append(" (PID: $pid)")
|
||||
append("\n")
|
||||
}
|
||||
}
|
||||
} else if (elementsArr != null) {
|
||||
append("🖥️ ${elementsArr.size()} interactable elements")
|
||||
if (mode != null) append(" (mode: $mode)")
|
||||
if (width != null && height != null) append(" • ${width}x$height")
|
||||
if (textSummary != null) append("\n\n$textSummary")
|
||||
// Show first few elements as preview
|
||||
val previewCount = minOf(elementsArr.size(), 5)
|
||||
if (previewCount > 0) {
|
||||
append("\n\n")
|
||||
for (i in 0 until previewCount) {
|
||||
val el = elementsArr[i]
|
||||
if (el.isJsonObject) {
|
||||
val idx = el.asJsonObject.get("index")?.takeIf { !it.isJsonNull }?.asInt
|
||||
val role = el.asJsonObject.get("role")?.takeIf { !it.isJsonNull }?.asString
|
||||
val label = el.asJsonObject.get("label")?.takeIf { !it.isJsonNull }?.asString
|
||||
append(" #$idx ${role ?: ""}${label?.let { " '$it'" } ?: ""}\n")
|
||||
}
|
||||
}
|
||||
if (elementsArr.size() > previewCount) {
|
||||
append(" ... and ${elementsArr.size() - previewCount} more")
|
||||
}
|
||||
}
|
||||
} else if (action != null) {
|
||||
append("$action:")
|
||||
if (ok != null) append(" ${if (ok) "✅ ok" else "❌ failed"}")
|
||||
if (message != null) append("\n$message")
|
||||
} else {
|
||||
append("🖥️ Computer use action")
|
||||
}
|
||||
if (visionAnalysis != null) {
|
||||
append("\n\n━━━ Vision Analysis ━━━\n$visionAnalysis")
|
||||
}
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryLine,
|
||||
mainOutput = mainBody,
|
||||
durationSec = duration,
|
||||
isRunning = isRunning,
|
||||
)
|
||||
}
|
||||
|
||||
// Check if this looks like a terminal result (check dataSource for backward compat)
|
||||
val hasStdout = dataSource.has("stdout")
|
||||
val hasStderr = dataSource.has("stderr")
|
||||
|
||||
Reference in new issue
Block a user