From 7b7ea59a377c2cc58f90f02b72659aded2184141 Mon Sep 17 00:00:00 2001 From: 0xallam Date: Fri, 9 Jan 2026 16:32:40 -0800 Subject: [PATCH] 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. --- .../tool_components/file_edit_renderer.py | 3 ++ .../tool_components/notes_renderer.py | 5 +++- .../tool_components/proxy_renderer.py | 30 +++++++++++++++---- .../tool_components/python_renderer.py | 10 +++++-- .../tool_components/terminal_renderer.py | 10 +++++-- .../tool_components/todo_renderer.py | 30 +++++++++++++++---- 6 files changed, 71 insertions(+), 17 deletions(-) diff --git a/strix/interface/tool_components/file_edit_renderer.py b/strix/interface/tool_components/file_edit_renderer.py index 8b3ca40..2290863 100644 --- a/strix/interface/tool_components/file_edit_renderer.py +++ b/strix/interface/tool_components/file_edit_renderer.py @@ -110,6 +110,9 @@ class StrReplaceEditorRenderer(BaseToolRenderer): text.append(" ") 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: text.append(" ") text.append("Processing...", style="dim") diff --git a/strix/interface/tool_components/notes_renderer.py b/strix/interface/tool_components/notes_renderer.py index 4660a27..f7ac24d 100644 --- a/strix/interface/tool_components/notes_renderer.py +++ b/strix/interface/tool_components/notes_renderer.py @@ -102,7 +102,10 @@ class ListNotesRenderer(BaseToolRenderer): text.append("πŸ“ ") 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) notes = result.get("notes", []) or [] diff --git a/strix/interface/tool_components/proxy_renderer.py b/strix/interface/tool_components/proxy_renderer.py index 0d87433..13b04cb 100644 --- a/strix/interface/tool_components/proxy_renderer.py +++ b/strix/interface/tool_components/proxy_renderer.py @@ -23,7 +23,10 @@ class ListRequestsRenderer(BaseToolRenderer): text.append("πŸ“‹ ") 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"] if isinstance(requests, list) and requests: for req in requests[:25]: @@ -70,7 +73,10 @@ class ViewRequestRenderer(BaseToolRenderer): text.append("πŸ‘€ ") 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: content = result["content"] content_preview = content[:2000] + "..." if len(content) > 2000 else content @@ -117,7 +123,10 @@ class SendRequestRenderer(BaseToolRenderer): text.append("πŸ“€ ") 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") response_body = result.get("body", "") @@ -161,7 +170,10 @@ class RepeatRequestRenderer(BaseToolRenderer): text.append("πŸ”„ ") 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") response_body = result.get("body", "") @@ -220,7 +232,10 @@ class ListSitemapRenderer(BaseToolRenderer): text.append("πŸ—ΊοΈ ") 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"] if isinstance(entries, list) and entries: for entry in entries[:30]: @@ -256,7 +271,10 @@ class ViewSitemapEntryRenderer(BaseToolRenderer): text.append("πŸ“ ") 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"] if isinstance(entry, dict): label = entry.get("label", "") diff --git a/strix/interface/tool_components/python_renderer.py b/strix/interface/tool_components/python_renderer.py index f20e036..415660e 100644 --- a/strix/interface/tool_components/python_renderer.py +++ b/strix/interface/tool_components/python_renderer.py @@ -104,7 +104,13 @@ class PythonRenderer(BaseToolRenderer): return text @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", "") stderr = result.get("stderr", "") @@ -143,7 +149,7 @@ class PythonRenderer(BaseToolRenderer): else: text.append("Running...", style="dim") - if result and isinstance(result, dict): + if result: cls._append_output(text, result) css_classes = cls.get_css_classes(status) diff --git a/strix/interface/tool_components/terminal_renderer.py b/strix/interface/tool_components/terminal_renderer.py index f6334a5..a510cf9 100644 --- a/strix/interface/tool_components/terminal_renderer.py +++ b/strix/interface/tool_components/terminal_renderer.py @@ -140,7 +140,7 @@ class TerminalRenderer(BaseToolRenderer): @classmethod 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() terminal_icon = ">_" @@ -208,8 +208,14 @@ class TerminalRenderer(BaseToolRenderer): @classmethod 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: + if isinstance(result, str): + if result.strip(): + text.append("\n") + text.append_text(cls._format_output(result)) + return + raw_output = result.get("content", "") output = cls._clean_output(raw_output, command) error = result.get("error") diff --git a/strix/interface/tool_components/todo_renderer.py b/strix/interface/tool_components/todo_renderer.py index 6224f9f..d166864 100644 --- a/strix/interface/tool_components/todo_renderer.py +++ b/strix/interface/tool_components/todo_renderer.py @@ -52,7 +52,10 @@ class CreateTodoRenderer(BaseToolRenderer): text.append("πŸ“‹ ") 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"): _format_todo_lines(text, result) else: @@ -80,7 +83,10 @@ class ListTodosRenderer(BaseToolRenderer): text.append("πŸ“‹ ") 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"): _format_todo_lines(text, result) else: @@ -108,7 +114,10 @@ class UpdateTodoRenderer(BaseToolRenderer): text.append("πŸ“‹ ") 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"): _format_todo_lines(text, result) else: @@ -136,7 +145,10 @@ class MarkTodoDoneRenderer(BaseToolRenderer): text.append("πŸ“‹ ") 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"): _format_todo_lines(text, result) else: @@ -164,7 +176,10 @@ class MarkTodoPendingRenderer(BaseToolRenderer): text.append("πŸ“‹ ") 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"): _format_todo_lines(text, result) else: @@ -192,7 +207,10 @@ class DeleteTodoRenderer(BaseToolRenderer): text.append("πŸ“‹ ") 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"): _format_todo_lines(text, result) else: