Refactor parseToolOutput by extracting TODO tool formatting logic (#514)

The parseToolOutput function in ChatBubble.kt was handling the specific
parsing and formatting logic for the "todo" tool inline, making the
function long and harder to read.

This commit extracts the todo-specific formatting logic into a new
private function formatTodoToolOutput, improving the readability and
maintainability of ChatBubble.kt without changing any behavior.
This commit is contained in:
M57 authored and GitHub committed 2026-07-09 18:20:20 +03:00
1 parent e90b69d215
commit bab0c3038d
1 file changed
+74 -61
@@ -532,6 +532,76 @@ data class ParsedToolData(
val isRunning: Boolean = false,
)
private fun formatTodoToolOutput(
dataSource: JsonObject,
args: Map<String, Any?>,
obj: JsonObject,
resolvedToolName: String?,
isRunning: Boolean,
): ParsedToolData {
val todosArray =
dataSource
.get("todos")
?.takeIf { !it.isJsonNull && it.isJsonArray }
?.asJsonArray
val summaryObj =
dataSource
.get("summary")
?.takeIf { !it.isJsonNull && it.isJsonObject }
?.asJsonObject
val todoSummaryText =
summaryObj?.let { s ->
val total = s.get("total")?.asDouble?.toInt() ?: 0
val pending = s.get("pending")?.asDouble?.toInt() ?: 0
val inProgress = s.get("in_progress")?.asDouble?.toInt() ?: 0
val completed = s.get("completed")?.asDouble?.toInt() ?: 0
val cancelled = s.get("cancelled")?.asDouble?.toInt() ?: 0
val parts = mutableListOf<String>()
if (pending > 0) parts.add("$pending pending")
if (inProgress > 0) parts.add("$inProgress in_progress")
if (completed > 0) parts.add("$completed completed")
if (cancelled > 0) parts.add("$cancelled cancelled")
val itemWord = if (total == 1) "item" else "items"
if (parts.isEmpty()) {
"📋 0 items"
} else {
"📋 $total $itemWord (${parts.joinToString(", ")})"
}
}
val formattedTodos =
todosArray
?.mapNotNull { element ->
if (!element.isJsonObject) return@mapNotNull null
val item = element.asJsonObject
val id = item.get("id")?.asString ?: ""
val content = item.get("content")?.asString ?: ""
val status = item.get("status")?.asString ?: "pending"
val marker =
when (status) {
"completed" -> "[x]"
"in_progress" -> "[>]"
"cancelled" -> "[~]"
else -> "[ ]"
}
"$marker $id. $content"
}?.joinToString("\n")
?.takeIf { it.isNotEmpty() }
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 = todoSummaryText,
mainOutput = formattedTodos,
durationSec = duration,
isRunning = isRunning,
)
}
/**
* Parses the tool.complete JSON payload into a [ParsedToolData].
* The gateway sends a payload with `args` and `result` sub-objects
@@ -583,69 +653,12 @@ fun parseToolOutput(
?.let { "${config.summaryPrefix}$it" }
}
// ── Todo-specific formatting ──
// Extract the structured todos array and summary object from the result,
// build a clean one-line summary and formatted item list.
if (resolvedToolName == "todo") {
val todosArray =
dataSource
.get("todos")
?.takeIf { !it.isJsonNull && it.isJsonArray }
?.asJsonArray
val summaryObj =
dataSource
.get("summary")
?.takeIf { !it.isJsonNull && it.isJsonObject }
?.asJsonObject
val todoSummaryText =
summaryObj?.let { s ->
val total = s.get("total")?.asDouble?.toInt() ?: 0
val pending = s.get("pending")?.asDouble?.toInt() ?: 0
val inProgress = s.get("in_progress")?.asDouble?.toInt() ?: 0
val completed = s.get("completed")?.asDouble?.toInt() ?: 0
val cancelled = s.get("cancelled")?.asDouble?.toInt() ?: 0
val parts = mutableListOf<String>()
if (pending > 0) parts.add("$pending pending")
if (inProgress > 0) parts.add("$inProgress in_progress")
if (completed > 0) parts.add("$completed completed")
if (cancelled > 0) parts.add("$cancelled cancelled")
val itemWord = if (total == 1) "item" else "items"
if (parts.isEmpty()) {
"📋 0 items"
} else {
"📋 $total $itemWord (${parts.joinToString(", ")})"
}
}
val formattedTodos =
todosArray
?.mapNotNull { element ->
if (!element.isJsonObject) return@mapNotNull null
val item = element.asJsonObject
val id = item.get("id")?.asString ?: ""
val content = item.get("content")?.asString ?: ""
val status = item.get("status")?.asString ?: "pending"
val marker =
when (status) {
"completed" -> "[x]"
"in_progress" -> "[>]"
"cancelled" -> "[~]"
else -> "[ ]"
}
"$marker $id. $content"
}?.joinToString("\n")
?.takeIf { it.isNotEmpty() }
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
return ParsedToolData(
toolName = resolvedToolName ?: "",
return formatTodoToolOutput(
dataSource = dataSource,
args = args,
result = dataSource.entrySet().associate { it.key to it.value.toString() },
summaryText = todoSummaryText,
mainOutput = formattedTodos,
durationSec = duration,
obj = obj,
resolvedToolName = resolvedToolName,
isRunning = isRunning,
)
}