import Foundation enum ToolResultTextFormatter { static func format(text: String, toolName: String?) -> String { let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return "" } guard self.looksLikeJSON(trimmed), let data = trimmed.data(using: .utf8), let json = try? JSONSerialization.jsonObject(with: data) else { return trimmed } let normalizedTool = toolName?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() return self.renderJSON(json, toolName: normalizedTool) } private static func looksLikeJSON(_ value: String) -> Bool { guard let first = value.first else { return false } return first == "{" || first == "[" } private static func renderJSON(_ json: Any, toolName: String?) -> String { if let dict = json as? [String: Any] { return self.renderDictionary(dict, toolName: toolName) } if let array = json as? [Any] { if array.isEmpty { return "No items." } return "\(array.count) item\(array.count == 1 ? "" : "s")." } return "" } private static func renderDictionary(_ dict: [String: Any], toolName: String?) -> String { let status = (dict["status"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) let errorText = self.firstString(in: dict, keys: ["error", "reason"]) let messageText = self.firstString(in: dict, keys: ["message", "result", "detail"]) if status?.lowercased() == "error" || errorText != nil { if let errorText { return "Error: \(self.sanitizeError(errorText))" } if let messageText { return "Error: \(self.sanitizeError(messageText))" } return "Error" } if toolName == "nodes", let summary = self.renderNodesSummary(dict) { return summary } if let message = messageText { return message } if let status, !status.isEmpty { return "Status: \(status)" } return "" } private static func renderNodesSummary(_ dict: [String: Any]) -> String? { if let nodes = dict["nodes"] as? [[String: Any]] { if nodes.isEmpty { return "No nodes found." } var lines: [String] = [] lines.append("\(nodes.count) node\(nodes.count == 1 ? "" : "s") found.") for node in nodes.prefix(3) { let label = self.firstString(in: node, keys: ["displayName", "name", "nodeId"]) ?? "Node" var details: [String] = [] if let connected = node["connected"] as? Bool { details.append(connected ? "connected" : "offline") } if let platform = self.firstString(in: node, keys: ["platform"]) { details.append(platform) } if let version = self.firstString(in: node, keys: ["osVersion", "appVersion", "version"]) { details.append(version) } if let pairing = self.pairingDetail(node) { details.append(pairing) } if details.isEmpty { lines.append("• \(label)") } else { lines.append("• \(label) - \(details.joined(separator: ", "))") } } let extra = nodes.count - 3 if extra > 0 { lines.append("... +\(extra) more") } return lines.joined(separator: "\n") } if let pending = dict["pending"] as? [Any], let paired = dict["paired"] as? [Any] { return "Pairing requests: \(pending.count) pending, \(paired.count) paired." } if let pending = dict["pending"] as? [Any] { if pending.isEmpty { return "No pending pairing requests." } return "\(pending.count) pending pairing request\(pending.count == 1 ? "" : "s")." } return nil } private static func pairingDetail(_ node: [String: Any]) -> String? { if let paired = node["paired"] as? Bool, !paired { return "pairing required" } for key in ["status", "state", "deviceStatus"] { if let raw = node[key] as? String, raw.lowercased().contains("pairing required") { return "pairing required" } } return nil } private static func firstString(in dict: [String: Any], keys: [String]) -> String? { for key in keys { if let value = dict[key] as? String { let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) if !trimmed.isEmpty { return trimmed } } } return nil } private static func sanitizeError(_ raw: String) -> String { var cleaned = raw.trimmingCharacters(in: .whitespacesAndNewlines) if cleaned.contains("agent="), cleaned.contains("action="), let marker = cleaned.range(of: ": ") { cleaned = String(cleaned[marker.upperBound...]).trimmingCharacters(in: .whitespacesAndNewlines) } if let firstLine = cleaned.split(separator: "\n").first { cleaned = String(firstLine).trimmingCharacters(in: .whitespacesAndNewlines) } if cleaned.count > 220 { cleaned = String(cleaned.prefix(217)) + "..." } return cleaned } }