fix: handle string results in tool renderers

Previously, tool renderers assumed result was always a dict and would
crash with AttributeError when result was a string (e.g., error messages).
Now all renderers properly check for string results and display them.
This commit is contained in:
0xallam
2026-01-09 16:32:40 -08:00
committed by Ahmed Allam
parent 226678f3f2
commit 7b7ea59a37
6 changed files with 71 additions and 17 deletions

View File

@@ -110,6 +110,9 @@ class StrReplaceEditorRenderer(BaseToolRenderer):
text.append(" ") text.append(" ")
text.append(line) text.append(line)
elif isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif not (result and isinstance(result, dict) and "content" in result) and not path: elif not (result and isinstance(result, dict) and "content" in result) and not path:
text.append(" ") text.append(" ")
text.append("Processing...", style="dim") text.append("Processing...", style="dim")

View File

@@ -102,7 +102,10 @@ class ListNotesRenderer(BaseToolRenderer):
text.append("📝 ") text.append("📝 ")
text.append("Notes", style="bold #fbbf24") text.append("Notes", style="bold #fbbf24")
if result and isinstance(result, dict) and result.get("success"): if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict) and result.get("success"):
count = result.get("total_count", 0) count = result.get("total_count", 0)
notes = result.get("notes", []) or [] notes = result.get("notes", []) or []

View File

@@ -23,7 +23,10 @@ class ListRequestsRenderer(BaseToolRenderer):
text.append("📋 ") text.append("📋 ")
text.append("Listing requests", style="bold #06b6d4") text.append("Listing requests", style="bold #06b6d4")
if result and isinstance(result, dict) and "requests" in result: if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict) and "requests" in result:
requests = result["requests"] requests = result["requests"]
if isinstance(requests, list) and requests: if isinstance(requests, list) and requests:
for req in requests[:25]: for req in requests[:25]:
@@ -70,7 +73,10 @@ class ViewRequestRenderer(BaseToolRenderer):
text.append("👀 ") text.append("👀 ")
text.append(f"Viewing {part}", style="bold #06b6d4") text.append(f"Viewing {part}", style="bold #06b6d4")
if result and isinstance(result, dict): if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict):
if "content" in result: if "content" in result:
content = result["content"] content = result["content"]
content_preview = content[:2000] + "..." if len(content) > 2000 else content content_preview = content[:2000] + "..." if len(content) > 2000 else content
@@ -117,7 +123,10 @@ class SendRequestRenderer(BaseToolRenderer):
text.append("📤 ") text.append("📤 ")
text.append(f"Sending {method}", style="bold #06b6d4") text.append(f"Sending {method}", style="bold #06b6d4")
if result and isinstance(result, dict): if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict):
status_code = result.get("status_code") status_code = result.get("status_code")
response_body = result.get("body", "") response_body = result.get("body", "")
@@ -161,7 +170,10 @@ class RepeatRequestRenderer(BaseToolRenderer):
text.append("🔄 ") text.append("🔄 ")
text.append("Repeating request", style="bold #06b6d4") text.append("Repeating request", style="bold #06b6d4")
if result and isinstance(result, dict): if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict):
status_code = result.get("status_code") status_code = result.get("status_code")
response_body = result.get("body", "") response_body = result.get("body", "")
@@ -220,7 +232,10 @@ class ListSitemapRenderer(BaseToolRenderer):
text.append("🗺️ ") text.append("🗺️ ")
text.append("Listing sitemap", style="bold #06b6d4") text.append("Listing sitemap", style="bold #06b6d4")
if result and isinstance(result, dict) and "entries" in result: if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict) and "entries" in result:
entries = result["entries"] entries = result["entries"]
if isinstance(entries, list) and entries: if isinstance(entries, list) and entries:
for entry in entries[:30]: for entry in entries[:30]:
@@ -256,7 +271,10 @@ class ViewSitemapEntryRenderer(BaseToolRenderer):
text.append("📍 ") text.append("📍 ")
text.append("Viewing sitemap entry", style="bold #06b6d4") text.append("Viewing sitemap entry", style="bold #06b6d4")
if result and isinstance(result, dict) and "entry" in result: if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict) and "entry" in result:
entry = result["entry"] entry = result["entry"]
if isinstance(entry, dict): if isinstance(entry, dict):
label = entry.get("label", "") label = entry.get("label", "")

View File

@@ -104,7 +104,13 @@ class PythonRenderer(BaseToolRenderer):
return text return text
@classmethod @classmethod
def _append_output(cls, text: Text, result: dict[str, Any]) -> None: def _append_output(cls, text: Text, result: dict[str, Any] | str) -> None:
if isinstance(result, str):
if result.strip():
text.append("\n")
text.append_text(cls._format_output(result))
return
stdout = result.get("stdout", "") stdout = result.get("stdout", "")
stderr = result.get("stderr", "") stderr = result.get("stderr", "")
@@ -143,7 +149,7 @@ class PythonRenderer(BaseToolRenderer):
else: else:
text.append("Running...", style="dim") text.append("Running...", style="dim")
if result and isinstance(result, dict): if result:
cls._append_output(text, result) cls._append_output(text, result)
css_classes = cls.get_css_classes(status) css_classes = cls.get_css_classes(status)

View File

@@ -140,7 +140,7 @@ class TerminalRenderer(BaseToolRenderer):
@classmethod @classmethod
def _build_content( def _build_content(
cls, command: str, is_input: bool, status: str, result: dict[str, Any] | None cls, command: str, is_input: bool, status: str, result: dict[str, Any] | str | None
) -> Text: ) -> Text:
text = Text() text = Text()
terminal_icon = ">_" terminal_icon = ">_"
@@ -208,8 +208,14 @@ class TerminalRenderer(BaseToolRenderer):
@classmethod @classmethod
def _append_output( def _append_output(
cls, text: Text, result: dict[str, Any], tool_status: str, command: str = "" cls, text: Text, result: dict[str, Any] | str, tool_status: str, command: str = ""
) -> None: ) -> None:
if isinstance(result, str):
if result.strip():
text.append("\n")
text.append_text(cls._format_output(result))
return
raw_output = result.get("content", "") raw_output = result.get("content", "")
output = cls._clean_output(raw_output, command) output = cls._clean_output(raw_output, command)
error = result.get("error") error = result.get("error")

View File

@@ -52,7 +52,10 @@ class CreateTodoRenderer(BaseToolRenderer):
text.append("📋 ") text.append("📋 ")
text.append("Todo", style="bold #a78bfa") text.append("Todo", style="bold #a78bfa")
if result and isinstance(result, dict): if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict):
if result.get("success"): if result.get("success"):
_format_todo_lines(text, result) _format_todo_lines(text, result)
else: else:
@@ -80,7 +83,10 @@ class ListTodosRenderer(BaseToolRenderer):
text.append("📋 ") text.append("📋 ")
text.append("Todos", style="bold #a78bfa") text.append("Todos", style="bold #a78bfa")
if result and isinstance(result, dict): if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict):
if result.get("success"): if result.get("success"):
_format_todo_lines(text, result) _format_todo_lines(text, result)
else: else:
@@ -108,7 +114,10 @@ class UpdateTodoRenderer(BaseToolRenderer):
text.append("📋 ") text.append("📋 ")
text.append("Todo Updated", style="bold #a78bfa") text.append("Todo Updated", style="bold #a78bfa")
if result and isinstance(result, dict): if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict):
if result.get("success"): if result.get("success"):
_format_todo_lines(text, result) _format_todo_lines(text, result)
else: else:
@@ -136,7 +145,10 @@ class MarkTodoDoneRenderer(BaseToolRenderer):
text.append("📋 ") text.append("📋 ")
text.append("Todo Completed", style="bold #a78bfa") text.append("Todo Completed", style="bold #a78bfa")
if result and isinstance(result, dict): if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict):
if result.get("success"): if result.get("success"):
_format_todo_lines(text, result) _format_todo_lines(text, result)
else: else:
@@ -164,7 +176,10 @@ class MarkTodoPendingRenderer(BaseToolRenderer):
text.append("📋 ") text.append("📋 ")
text.append("Todo Reopened", style="bold #f59e0b") text.append("Todo Reopened", style="bold #f59e0b")
if result and isinstance(result, dict): if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict):
if result.get("success"): if result.get("success"):
_format_todo_lines(text, result) _format_todo_lines(text, result)
else: else:
@@ -192,7 +207,10 @@ class DeleteTodoRenderer(BaseToolRenderer):
text.append("📋 ") text.append("📋 ")
text.append("Todo Removed", style="bold #94a3b8") text.append("Todo Removed", style="bold #94a3b8")
if result and isinstance(result, dict): if isinstance(result, str) and result.strip():
text.append("\n ")
text.append(result.strip(), style="dim")
elif result and isinstance(result, dict):
if result.get("success"): if result.get("success"):
_format_todo_lines(text, result) _format_todo_lines(text, result)
else: else: