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:
1 file changed
+67
-54
@@ -532,61 +532,13 @@ data class ParsedToolData(
|
|||||||
val isRunning: Boolean = false,
|
val isRunning: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
private fun formatTodoToolOutput(
|
||||||
* Parses the tool.complete JSON payload into a [ParsedToolData].
|
dataSource: JsonObject,
|
||||||
* The gateway sends a payload with `args` and `result` sub-objects
|
args: Map<String, Any?>,
|
||||||
* (see tui_gateway/server.py `_on_tool_complete`). We extract both
|
obj: JsonObject,
|
||||||
* and use the tool name to build a one-line summary from the relevant arg.
|
resolvedToolName: String?,
|
||||||
*/
|
|
||||||
fun parseToolOutput(
|
|
||||||
content: String,
|
|
||||||
toolName: String?,
|
|
||||||
isRunning: Boolean,
|
isRunning: Boolean,
|
||||||
): ParsedToolData? {
|
): ParsedToolData {
|
||||||
val trimmed = content.trim()
|
|
||||||
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null
|
|
||||||
return try {
|
|
||||||
val element = OkHttpProvider.json.parseToJsonElement(trimmed)
|
|
||||||
if (!element.isJsonObject) return null
|
|
||||||
val obj = element.asJsonObject
|
|
||||||
|
|
||||||
// Resolve tool name — from parameter first, then from payload (old sessions)
|
|
||||||
val resolvedToolName =
|
|
||||||
toolName
|
|
||||||
?: obj.get("name")?.takeIf { !it.isJsonNull }?.asString
|
|
||||||
|
|
||||||
val config = ToolSchemaRegistry.getDisplayConfig(resolvedToolName)
|
|
||||||
|
|
||||||
// Extract args sub-object if present (new tool.complete format)
|
|
||||||
val argsObj = obj.get("args")?.takeIf { !it.isJsonNull && it.isJsonObject }?.asJsonObject
|
|
||||||
val args: Map<String, Any?> =
|
|
||||||
argsObj?.entrySet()?.associate { entry ->
|
|
||||||
entry.key to (if (entry.value.isJsonPrimitive) entry.value.asString else entry.value.toString())
|
|
||||||
} ?: emptyMap()
|
|
||||||
|
|
||||||
// Extract result sub-object if present, fall back to top-level (old format)
|
|
||||||
val resultObj = obj.get("result")?.takeIf { !it.isJsonNull && it.isJsonObject }?.asJsonObject
|
|
||||||
val dataSource = resultObj ?: obj
|
|
||||||
|
|
||||||
// Build summary line from args (new format) or context field (old tool.start)
|
|
||||||
val summaryText =
|
|
||||||
if (config.summaryArgKey != null) {
|
|
||||||
val raw = args[config.summaryArgKey]?.toString() ?: ""
|
|
||||||
val truncated = if (raw.length > 100) raw.take(100) + "…" else raw
|
|
||||||
if (truncated.isNotBlank()) "${config.summaryPrefix}$truncated" else null
|
|
||||||
} else {
|
|
||||||
null ?: obj
|
|
||||||
.get("context")
|
|
||||||
?.takeIf { !it.isJsonNull }
|
|
||||||
?.asString
|
|
||||||
?.takeIf { it.isNotEmpty() }
|
|
||||||
?.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 =
|
val todosArray =
|
||||||
dataSource
|
dataSource
|
||||||
.get("todos")
|
.get("todos")
|
||||||
@@ -648,6 +600,67 @@ fun parseToolOutput(
|
|||||||
durationSec = duration,
|
durationSec = duration,
|
||||||
isRunning = isRunning,
|
isRunning = isRunning,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the tool.complete JSON payload into a [ParsedToolData].
|
||||||
|
* The gateway sends a payload with `args` and `result` sub-objects
|
||||||
|
* (see tui_gateway/server.py `_on_tool_complete`). We extract both
|
||||||
|
* and use the tool name to build a one-line summary from the relevant arg.
|
||||||
|
*/
|
||||||
|
fun parseToolOutput(
|
||||||
|
content: String,
|
||||||
|
toolName: String?,
|
||||||
|
isRunning: Boolean,
|
||||||
|
): ParsedToolData? {
|
||||||
|
val trimmed = content.trim()
|
||||||
|
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null
|
||||||
|
return try {
|
||||||
|
val element = OkHttpProvider.json.parseToJsonElement(trimmed)
|
||||||
|
if (!element.isJsonObject) return null
|
||||||
|
val obj = element.asJsonObject
|
||||||
|
|
||||||
|
// Resolve tool name — from parameter first, then from payload (old sessions)
|
||||||
|
val resolvedToolName =
|
||||||
|
toolName
|
||||||
|
?: obj.get("name")?.takeIf { !it.isJsonNull }?.asString
|
||||||
|
|
||||||
|
val config = ToolSchemaRegistry.getDisplayConfig(resolvedToolName)
|
||||||
|
|
||||||
|
// Extract args sub-object if present (new tool.complete format)
|
||||||
|
val argsObj = obj.get("args")?.takeIf { !it.isJsonNull && it.isJsonObject }?.asJsonObject
|
||||||
|
val args: Map<String, Any?> =
|
||||||
|
argsObj?.entrySet()?.associate { entry ->
|
||||||
|
entry.key to (if (entry.value.isJsonPrimitive) entry.value.asString else entry.value.toString())
|
||||||
|
} ?: emptyMap()
|
||||||
|
|
||||||
|
// Extract result sub-object if present, fall back to top-level (old format)
|
||||||
|
val resultObj = obj.get("result")?.takeIf { !it.isJsonNull && it.isJsonObject }?.asJsonObject
|
||||||
|
val dataSource = resultObj ?: obj
|
||||||
|
|
||||||
|
// Build summary line from args (new format) or context field (old tool.start)
|
||||||
|
val summaryText =
|
||||||
|
if (config.summaryArgKey != null) {
|
||||||
|
val raw = args[config.summaryArgKey]?.toString() ?: ""
|
||||||
|
val truncated = if (raw.length > 100) raw.take(100) + "…" else raw
|
||||||
|
if (truncated.isNotBlank()) "${config.summaryPrefix}$truncated" else null
|
||||||
|
} else {
|
||||||
|
null ?: obj
|
||||||
|
.get("context")
|
||||||
|
?.takeIf { !it.isJsonNull }
|
||||||
|
?.asString
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
?.let { "${config.summaryPrefix}$it" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedToolName == "todo") {
|
||||||
|
return formatTodoToolOutput(
|
||||||
|
dataSource = dataSource,
|
||||||
|
args = args,
|
||||||
|
obj = obj,
|
||||||
|
resolvedToolName = resolvedToolName,
|
||||||
|
isRunning = isRunning,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Fact Store-specific formatting ──
|
// ── Fact Store-specific formatting ──
|
||||||
|
|||||||
Reference in new issue
Block a user