refactor: revamp proxy tool renderers for better UX
- Show actual request/response data with visual flow (>> / <<) - Display all relevant params: filters, sort, scope, modifications - Add type-safe handling for streaming edge cases - Use color-coded status codes (2xx green, 3xx yellow, 4xx/5xx red) - Show search context (before/after) not just matched text - Show full request details in send/repeat request renderers - Show modifications on separate lines with full content - Increase truncation limits for better visibility (200 char lines) - Use present tense lowercase titles (listing, viewing, searching)
This commit is contained in:
@@ -7,53 +7,105 @@ from .base_renderer import BaseToolRenderer
|
|||||||
from .registry import register_tool_renderer
|
from .registry import register_tool_renderer
|
||||||
|
|
||||||
|
|
||||||
|
PROXY_ICON = "<~>"
|
||||||
|
MAX_REQUESTS_DISPLAY = 20
|
||||||
|
MAX_LINE_LENGTH = 200
|
||||||
|
|
||||||
|
|
||||||
|
def _truncate(text: str, max_len: int = 80) -> str:
|
||||||
|
return text[: max_len - 3] + "..." if len(text) > max_len else text
|
||||||
|
|
||||||
|
|
||||||
|
def _sanitize(text: str, max_len: int = 150) -> str:
|
||||||
|
"""Remove newlines and truncate text."""
|
||||||
|
clean = text.replace("\n", " ").replace("\r", "").replace("\t", " ")
|
||||||
|
return _truncate(clean, max_len)
|
||||||
|
|
||||||
|
|
||||||
|
def _status_style(code: int | None) -> str:
|
||||||
|
if code is None:
|
||||||
|
return "dim"
|
||||||
|
if 200 <= code < 300:
|
||||||
|
return "#22c55e" # green
|
||||||
|
if 300 <= code < 400:
|
||||||
|
return "#eab308" # yellow
|
||||||
|
if 400 <= code < 500:
|
||||||
|
return "#f97316" # orange
|
||||||
|
if code >= 500:
|
||||||
|
return "#ef4444" # red
|
||||||
|
return "dim"
|
||||||
|
|
||||||
|
|
||||||
@register_tool_renderer
|
@register_tool_renderer
|
||||||
class ListRequestsRenderer(BaseToolRenderer):
|
class ListRequestsRenderer(BaseToolRenderer):
|
||||||
tool_name: ClassVar[str] = "list_requests"
|
tool_name: ClassVar[str] = "list_requests"
|
||||||
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def render(cls, tool_data: dict[str, Any]) -> Static:
|
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: PLR0912 # noqa: PLR0912
|
||||||
args = tool_data.get("args", {})
|
args = tool_data.get("args", {})
|
||||||
result = tool_data.get("result")
|
result = tool_data.get("result")
|
||||||
|
status = tool_data.get("status", "running")
|
||||||
|
|
||||||
httpql_filter = args.get("httpql_filter")
|
httpql_filter = args.get("httpql_filter")
|
||||||
|
sort_by = args.get("sort_by")
|
||||||
|
sort_order = args.get("sort_order")
|
||||||
|
scope_id = args.get("scope_id")
|
||||||
|
|
||||||
text = Text()
|
text = Text()
|
||||||
text.append("📋 ")
|
text.append(PROXY_ICON, style="dim")
|
||||||
text.append("Listing requests", style="bold #06b6d4")
|
text.append(" listing requests", style="#06b6d4")
|
||||||
|
|
||||||
if isinstance(result, str) and result.strip():
|
if httpql_filter:
|
||||||
text.append("\n ")
|
text.append(f" where {_truncate(httpql_filter, 150)}", style="dim italic")
|
||||||
text.append(result.strip(), style="dim")
|
|
||||||
elif result and isinstance(result, dict) and "requests" in result:
|
meta_parts = []
|
||||||
requests = result["requests"]
|
if sort_by and sort_by != "timestamp":
|
||||||
if isinstance(requests, list) and requests:
|
meta_parts.append(f"by:{sort_by}")
|
||||||
for req in requests[:25]:
|
if sort_order and sort_order != "desc":
|
||||||
if isinstance(req, dict):
|
meta_parts.append(sort_order)
|
||||||
method = req.get("method", "?")
|
if scope_id and isinstance(scope_id, str):
|
||||||
path = req.get("path", "?")
|
meta_parts.append(f"scope:{scope_id[:8]}")
|
||||||
response = req.get("response") or {}
|
if meta_parts:
|
||||||
status = response.get("statusCode", "?")
|
text.append(f" ({', '.join(meta_parts)})", style="dim")
|
||||||
text.append("\n ")
|
|
||||||
text.append(f"{method} {path} → {status}", style="dim")
|
if status == "completed" and isinstance(result, dict):
|
||||||
if len(requests) > 25:
|
if "error" in result:
|
||||||
text.append("\n ")
|
text.append(f" error: {_sanitize(str(result['error']), 150)}", style="#ef4444")
|
||||||
text.append(f"... +{len(requests) - 25} more", style="dim")
|
|
||||||
else:
|
else:
|
||||||
text.append("\n ")
|
total = result.get("total_count", 0)
|
||||||
text.append("No requests found", style="dim")
|
requests = result.get("requests", [])
|
||||||
elif httpql_filter:
|
|
||||||
filter_display = (
|
|
||||||
httpql_filter[:500] + "..." if len(httpql_filter) > 500 else httpql_filter
|
|
||||||
)
|
|
||||||
text.append("\n ")
|
|
||||||
text.append(filter_display, style="dim")
|
|
||||||
else:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append("All requests", style="dim")
|
|
||||||
|
|
||||||
css_classes = cls.get_css_classes("completed")
|
text.append(f" [{total} found]", style="dim")
|
||||||
|
|
||||||
|
if requests and isinstance(requests, list):
|
||||||
|
text.append("\n")
|
||||||
|
for i, req in enumerate(requests[:MAX_REQUESTS_DISPLAY]):
|
||||||
|
if not isinstance(req, dict):
|
||||||
|
continue
|
||||||
|
method = req.get("method", "?")
|
||||||
|
host = req.get("host", "")
|
||||||
|
path = req.get("path", "/")
|
||||||
|
resp = req.get("response") or {}
|
||||||
|
code = resp.get("statusCode") if isinstance(resp, dict) else None
|
||||||
|
|
||||||
|
text.append(" ")
|
||||||
|
text.append(f"{method:6}", style="#a78bfa")
|
||||||
|
text.append(f" {_truncate(host + path, 180)}", style="dim")
|
||||||
|
if code:
|
||||||
|
text.append(f" {code}", style=_status_style(code))
|
||||||
|
|
||||||
|
if i < min(len(requests), MAX_REQUESTS_DISPLAY) - 1:
|
||||||
|
text.append("\n")
|
||||||
|
|
||||||
|
if len(requests) > MAX_REQUESTS_DISPLAY:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(
|
||||||
|
f" ... +{len(requests) - MAX_REQUESTS_DISPLAY} more",
|
||||||
|
style="dim italic",
|
||||||
|
)
|
||||||
|
|
||||||
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(text, classes=css_classes)
|
return Static(text, classes=css_classes)
|
||||||
|
|
||||||
|
|
||||||
@@ -63,46 +115,83 @@ class ViewRequestRenderer(BaseToolRenderer):
|
|||||||
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def render(cls, tool_data: dict[str, Any]) -> Static:
|
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: PLR0912, PLR0915
|
||||||
args = tool_data.get("args", {})
|
args = tool_data.get("args", {})
|
||||||
result = tool_data.get("result")
|
result = tool_data.get("result")
|
||||||
|
status = tool_data.get("status", "running")
|
||||||
|
|
||||||
|
request_id = args.get("request_id", "")
|
||||||
part = args.get("part", "request")
|
part = args.get("part", "request")
|
||||||
|
search_pattern = args.get("search_pattern")
|
||||||
|
|
||||||
text = Text()
|
text = Text()
|
||||||
text.append("👀 ")
|
text.append(PROXY_ICON, style="dim")
|
||||||
text.append(f"Viewing {part}", style="bold #06b6d4")
|
|
||||||
|
|
||||||
if isinstance(result, str) and result.strip():
|
action = "searching" if search_pattern else "viewing"
|
||||||
text.append("\n ")
|
text.append(f" {action} {part}", style="#06b6d4")
|
||||||
text.append(result.strip(), style="dim")
|
|
||||||
elif result and isinstance(result, dict):
|
if request_id:
|
||||||
if "content" in result:
|
text.append(f" #{request_id}", style="dim")
|
||||||
content = result["content"]
|
|
||||||
content_preview = content[:2000] + "..." if len(content) > 2000 else content
|
if search_pattern:
|
||||||
text.append("\n ")
|
text.append(f" /{_truncate(search_pattern, 100)}/", style="dim italic")
|
||||||
text.append(content_preview, style="dim")
|
|
||||||
|
if status == "completed" and isinstance(result, dict):
|
||||||
|
if "error" in result:
|
||||||
|
text.append(f" error: {_sanitize(str(result['error']), 150)}", style="#ef4444")
|
||||||
elif "matches" in result:
|
elif "matches" in result:
|
||||||
matches = result["matches"]
|
matches = result.get("matches", [])
|
||||||
if isinstance(matches, list) and matches:
|
total = result.get("total_matches", len(matches))
|
||||||
for match in matches[:25]:
|
text.append(f" [{total} matches]", style="dim")
|
||||||
if isinstance(match, dict) and "match" in match:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append(match["match"], style="dim")
|
|
||||||
if len(matches) > 25:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append(f"... +{len(matches) - 25} more matches", style="dim")
|
|
||||||
else:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append("No matches found", style="dim")
|
|
||||||
else:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append("Viewing content...", style="dim")
|
|
||||||
else:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append("Loading...", style="dim")
|
|
||||||
|
|
||||||
css_classes = cls.get_css_classes("completed")
|
if matches and isinstance(matches, list):
|
||||||
|
text.append("\n")
|
||||||
|
for i, m in enumerate(matches[:5]):
|
||||||
|
if not isinstance(m, dict):
|
||||||
|
continue
|
||||||
|
before = m.get("before", "") or ""
|
||||||
|
match_text = m.get("match", "") or ""
|
||||||
|
after = m.get("after", "") or ""
|
||||||
|
|
||||||
|
before = before.replace("\n", " ").replace("\r", "")[-100:]
|
||||||
|
after = after.replace("\n", " ").replace("\r", "")[:100]
|
||||||
|
|
||||||
|
text.append(" ")
|
||||||
|
|
||||||
|
if before:
|
||||||
|
text.append(f"...{before}", style="dim")
|
||||||
|
text.append(match_text, style="#22c55e bold")
|
||||||
|
if after:
|
||||||
|
text.append(f"{after}...", style="dim")
|
||||||
|
|
||||||
|
if i < min(len(matches), 5) - 1:
|
||||||
|
text.append("\n")
|
||||||
|
|
||||||
|
if len(matches) > 5:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(f" ... +{len(matches) - 5} more matches", style="dim italic")
|
||||||
|
|
||||||
|
elif "content" in result:
|
||||||
|
showing = result.get("showing_lines", "")
|
||||||
|
has_more = result.get("has_more", False)
|
||||||
|
content = result.get("content", "")
|
||||||
|
|
||||||
|
text.append(f" [{showing}]", style="dim")
|
||||||
|
|
||||||
|
if content and isinstance(content, str):
|
||||||
|
lines = content.split("\n")[:15]
|
||||||
|
text.append("\n")
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
text.append(" ")
|
||||||
|
text.append(_truncate(line, MAX_LINE_LENGTH), style="dim")
|
||||||
|
if i < len(lines) - 1:
|
||||||
|
text.append("\n")
|
||||||
|
|
||||||
|
if has_more or len(lines) > 15:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(" ... more content available", style="dim italic")
|
||||||
|
|
||||||
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(text, classes=css_classes)
|
return Static(text, classes=css_classes)
|
||||||
|
|
||||||
|
|
||||||
@@ -112,45 +201,71 @@ class SendRequestRenderer(BaseToolRenderer):
|
|||||||
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def render(cls, tool_data: dict[str, Any]) -> Static:
|
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: PLR0912, PLR0915
|
||||||
args = tool_data.get("args", {})
|
args = tool_data.get("args", {})
|
||||||
result = tool_data.get("result")
|
result = tool_data.get("result")
|
||||||
|
status = tool_data.get("status", "running")
|
||||||
|
|
||||||
method = args.get("method", "GET")
|
method = args.get("method", "GET")
|
||||||
url = args.get("url", "")
|
url = args.get("url", "")
|
||||||
|
req_headers = args.get("headers")
|
||||||
|
req_body = args.get("body", "")
|
||||||
|
|
||||||
text = Text()
|
text = Text()
|
||||||
text.append("📤 ")
|
text.append(PROXY_ICON, style="dim")
|
||||||
text.append(f"Sending {method}", style="bold #06b6d4")
|
text.append(" sending request", style="#06b6d4")
|
||||||
|
|
||||||
if isinstance(result, str) and result.strip():
|
text.append("\n")
|
||||||
text.append("\n ")
|
text.append(" >> ", style="#3b82f6")
|
||||||
text.append(result.strip(), style="dim")
|
text.append(method, style="#a78bfa")
|
||||||
elif result and isinstance(result, dict):
|
text.append(f" {_truncate(url, 180)}", style="dim")
|
||||||
status_code = result.get("status_code")
|
|
||||||
response_body = result.get("body", "")
|
|
||||||
|
|
||||||
if status_code:
|
if req_headers and isinstance(req_headers, dict):
|
||||||
text.append("\n ")
|
for k, v in list(req_headers.items())[:5]:
|
||||||
text.append(f"Status: {status_code}", style="dim")
|
text.append("\n")
|
||||||
if response_body:
|
text.append(" >> ", style="#3b82f6")
|
||||||
body_preview = (
|
text.append(f"{k}: ", style="dim")
|
||||||
response_body[:2000] + "..." if len(response_body) > 2000 else response_body
|
text.append(_sanitize(str(v), 150), style="dim")
|
||||||
)
|
|
||||||
text.append("\n ")
|
if req_body and isinstance(req_body, str):
|
||||||
text.append(body_preview, style="dim")
|
text.append("\n")
|
||||||
|
text.append(" >> ", style="#3b82f6")
|
||||||
|
body_lines = req_body.split("\n")[:4]
|
||||||
|
for i, line in enumerate(body_lines):
|
||||||
|
if i > 0:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(" ", style="dim")
|
||||||
|
text.append(_truncate(line, MAX_LINE_LENGTH), style="dim")
|
||||||
|
if len(req_body.split("\n")) > 4:
|
||||||
|
text.append(" ...", style="dim italic")
|
||||||
|
|
||||||
|
if status == "completed" and isinstance(result, dict):
|
||||||
|
if "error" in result:
|
||||||
|
text.append(f"\n error: {_sanitize(str(result['error']), 150)}", style="#ef4444")
|
||||||
else:
|
else:
|
||||||
text.append("\n ")
|
code = result.get("status_code")
|
||||||
text.append("Response received", style="dim")
|
time_ms = result.get("response_time_ms")
|
||||||
elif url:
|
|
||||||
url_display = url[:500] + "..." if len(url) > 500 else url
|
|
||||||
text.append("\n ")
|
|
||||||
text.append(url_display, style="dim")
|
|
||||||
else:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append("Sending...", style="dim")
|
|
||||||
|
|
||||||
css_classes = cls.get_css_classes("completed")
|
text.append("\n")
|
||||||
|
text.append(" << ", style="#22c55e")
|
||||||
|
if code:
|
||||||
|
text.append(f"{code}", style=_status_style(code))
|
||||||
|
if time_ms:
|
||||||
|
text.append(f" ({time_ms}ms)", style="dim")
|
||||||
|
|
||||||
|
body = result.get("body", "")
|
||||||
|
if body and isinstance(body, str):
|
||||||
|
lines = body.split("\n")[:6]
|
||||||
|
for line in lines:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(" << ", style="#22c55e")
|
||||||
|
text.append(_truncate(line, MAX_LINE_LENGTH - 5), style="dim")
|
||||||
|
|
||||||
|
if len(body.split("\n")) > 6:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(" ...", style="dim italic")
|
||||||
|
|
||||||
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(text, classes=css_classes)
|
return Static(text, classes=css_classes)
|
||||||
|
|
||||||
|
|
||||||
@@ -160,45 +275,99 @@ class RepeatRequestRenderer(BaseToolRenderer):
|
|||||||
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def render(cls, tool_data: dict[str, Any]) -> Static:
|
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: PLR0912, PLR0915
|
||||||
args = tool_data.get("args", {})
|
args = tool_data.get("args", {})
|
||||||
result = tool_data.get("result")
|
result = tool_data.get("result")
|
||||||
|
status = tool_data.get("status", "running")
|
||||||
|
|
||||||
modifications = args.get("modifications", {})
|
request_id = args.get("request_id", "")
|
||||||
|
modifications = args.get("modifications")
|
||||||
|
|
||||||
text = Text()
|
text = Text()
|
||||||
text.append("🔄 ")
|
text.append(PROXY_ICON, style="dim")
|
||||||
text.append("Repeating request", style="bold #06b6d4")
|
text.append(" repeating request", style="#06b6d4")
|
||||||
|
|
||||||
if isinstance(result, str) and result.strip():
|
if request_id:
|
||||||
text.append("\n ")
|
text.append(f" #{request_id}", style="dim")
|
||||||
text.append(result.strip(), style="dim")
|
|
||||||
elif result and isinstance(result, dict):
|
|
||||||
status_code = result.get("status_code")
|
|
||||||
response_body = result.get("body", "")
|
|
||||||
|
|
||||||
if status_code:
|
if modifications and isinstance(modifications, dict):
|
||||||
text.append("\n ")
|
text.append("\n modifications:", style="dim italic")
|
||||||
text.append(f"Status: {status_code}", style="dim")
|
|
||||||
if response_body:
|
if "url" in modifications:
|
||||||
body_preview = (
|
text.append("\n")
|
||||||
response_body[:2000] + "..." if len(response_body) > 2000 else response_body
|
text.append(" >> ", style="#3b82f6")
|
||||||
)
|
text.append(f"url: {_truncate(str(modifications['url']), 180)}", style="dim")
|
||||||
text.append("\n ")
|
|
||||||
text.append(body_preview, style="dim")
|
if "headers" in modifications and isinstance(modifications["headers"], dict):
|
||||||
|
for k, v in list(modifications["headers"].items())[:5]:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(" >> ", style="#3b82f6")
|
||||||
|
text.append(f"{k}: {_sanitize(str(v), 150)}", style="dim")
|
||||||
|
|
||||||
|
if "cookies" in modifications and isinstance(modifications["cookies"], dict):
|
||||||
|
for k, v in list(modifications["cookies"].items())[:5]:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(" >> ", style="#3b82f6")
|
||||||
|
text.append(f"cookie {k}={_sanitize(str(v), 100)}", style="dim")
|
||||||
|
|
||||||
|
if "params" in modifications and isinstance(modifications["params"], dict):
|
||||||
|
for k, v in list(modifications["params"].items())[:5]:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(" >> ", style="#3b82f6")
|
||||||
|
text.append(f"param {k}={_sanitize(str(v), 100)}", style="dim")
|
||||||
|
|
||||||
|
if "body" in modifications and isinstance(modifications["body"], str):
|
||||||
|
text.append("\n")
|
||||||
|
text.append(" >> ", style="#3b82f6")
|
||||||
|
body_lines = modifications["body"].split("\n")[:4]
|
||||||
|
for i, line in enumerate(body_lines):
|
||||||
|
if i > 0:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(" ", style="dim")
|
||||||
|
text.append(_truncate(line, MAX_LINE_LENGTH), style="dim")
|
||||||
|
if len(modifications["body"].split("\n")) > 4:
|
||||||
|
text.append(" ...", style="dim italic")
|
||||||
|
|
||||||
|
elif modifications and isinstance(modifications, str):
|
||||||
|
text.append(f"\n {_truncate(modifications, 200)}", style="dim italic")
|
||||||
|
|
||||||
|
if status == "completed" and isinstance(result, dict):
|
||||||
|
if "error" in result:
|
||||||
|
text.append(f"\n error: {_sanitize(str(result['error']), 150)}", style="#ef4444")
|
||||||
else:
|
else:
|
||||||
text.append("\n ")
|
req = result.get("request", {})
|
||||||
text.append("Response received", style="dim")
|
method = req.get("method", "")
|
||||||
elif modifications:
|
url = req.get("url", "")
|
||||||
mod_str = str(modifications)
|
code = result.get("status_code")
|
||||||
mod_display = mod_str[:500] + "..." if len(mod_str) > 500 else mod_str
|
time_ms = result.get("response_time_ms")
|
||||||
text.append("\n ")
|
|
||||||
text.append(mod_display, style="dim")
|
|
||||||
else:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append("No modifications", style="dim")
|
|
||||||
|
|
||||||
css_classes = cls.get_css_classes("completed")
|
text.append("\n")
|
||||||
|
text.append(" >> ", style="#3b82f6")
|
||||||
|
if method:
|
||||||
|
text.append(f"{method} ", style="#a78bfa")
|
||||||
|
if url:
|
||||||
|
text.append(_truncate(url, 180), style="dim")
|
||||||
|
|
||||||
|
text.append("\n")
|
||||||
|
text.append(" << ", style="#22c55e")
|
||||||
|
if code:
|
||||||
|
text.append(f"{code}", style=_status_style(code))
|
||||||
|
if time_ms:
|
||||||
|
text.append(f" ({time_ms}ms)", style="dim")
|
||||||
|
|
||||||
|
body = result.get("body", "")
|
||||||
|
if body and isinstance(body, str):
|
||||||
|
lines = body.split("\n")[:5]
|
||||||
|
for line in lines:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(" << ", style="#22c55e")
|
||||||
|
text.append(_truncate(line, MAX_LINE_LENGTH - 5), style="dim")
|
||||||
|
|
||||||
|
if len(body.split("\n")) > 5:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(" ...", style="dim italic")
|
||||||
|
|
||||||
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(text, classes=css_classes)
|
return Static(text, classes=css_classes)
|
||||||
|
|
||||||
|
|
||||||
@@ -208,14 +377,87 @@ class ScopeRulesRenderer(BaseToolRenderer):
|
|||||||
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: ARG003
|
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: PLR0912, PLR0915
|
||||||
text = Text()
|
args = tool_data.get("args", {})
|
||||||
text.append("⚙️ ")
|
result = tool_data.get("result")
|
||||||
text.append("Updating proxy scope", style="bold #06b6d4")
|
status = tool_data.get("status", "running")
|
||||||
text.append("\n ")
|
|
||||||
text.append("Configuring...", style="dim")
|
|
||||||
|
|
||||||
css_classes = cls.get_css_classes("completed")
|
action = args.get("action", "")
|
||||||
|
scope_name = args.get("scope_name", "")
|
||||||
|
scope_id = args.get("scope_id", "")
|
||||||
|
allowlist = args.get("allowlist")
|
||||||
|
denylist = args.get("denylist")
|
||||||
|
|
||||||
|
text = Text()
|
||||||
|
text.append(PROXY_ICON, style="dim")
|
||||||
|
|
||||||
|
action_map = {
|
||||||
|
"get": "getting",
|
||||||
|
"list": "listing",
|
||||||
|
"create": "creating",
|
||||||
|
"update": "updating",
|
||||||
|
"delete": "deleting",
|
||||||
|
}
|
||||||
|
action_text = action_map.get(action, action + "ing" if action else "managing")
|
||||||
|
text.append(f" {action_text} proxy scope", style="#06b6d4")
|
||||||
|
|
||||||
|
if scope_name:
|
||||||
|
text.append(f" '{_truncate(scope_name, 50)}'", style="dim italic")
|
||||||
|
if scope_id and isinstance(scope_id, str):
|
||||||
|
text.append(f" #{scope_id[:8]}", style="dim")
|
||||||
|
|
||||||
|
if allowlist and isinstance(allowlist, list):
|
||||||
|
allow_str = ", ".join(_truncate(str(a), 40) for a in allowlist[:4])
|
||||||
|
text.append(f"\n allow: {allow_str}", style="dim")
|
||||||
|
if len(allowlist) > 4:
|
||||||
|
text.append(f" +{len(allowlist) - 4}", style="dim italic")
|
||||||
|
if denylist and isinstance(denylist, list):
|
||||||
|
deny_str = ", ".join(_truncate(str(d), 40) for d in denylist[:4])
|
||||||
|
text.append(f"\n deny: {deny_str}", style="dim")
|
||||||
|
if len(denylist) > 4:
|
||||||
|
text.append(f" +{len(denylist) - 4}", style="dim italic")
|
||||||
|
|
||||||
|
if status == "completed" and isinstance(result, dict):
|
||||||
|
if "error" in result:
|
||||||
|
text.append(f" error: {_sanitize(str(result['error']), 150)}", style="#ef4444")
|
||||||
|
elif "scopes" in result:
|
||||||
|
scopes = result.get("scopes", [])
|
||||||
|
text.append(f" [{len(scopes)} scopes]", style="dim")
|
||||||
|
|
||||||
|
if scopes and isinstance(scopes, list):
|
||||||
|
text.append("\n")
|
||||||
|
for i, scope in enumerate(scopes[:5]):
|
||||||
|
if not isinstance(scope, dict):
|
||||||
|
continue
|
||||||
|
name = scope.get("name", "?")
|
||||||
|
allow = scope.get("allowlist") or []
|
||||||
|
text.append(" ")
|
||||||
|
text.append(_truncate(str(name), 40), style="#22c55e")
|
||||||
|
if allow and isinstance(allow, list):
|
||||||
|
allow_str = ", ".join(_truncate(str(a), 30) for a in allow[:3])
|
||||||
|
text.append(f" {allow_str}", style="dim")
|
||||||
|
if len(allow) > 3:
|
||||||
|
text.append(f" +{len(allow) - 3}", style="dim italic")
|
||||||
|
if i < min(len(scopes), 5) - 1:
|
||||||
|
text.append("\n")
|
||||||
|
|
||||||
|
elif "scope" in result:
|
||||||
|
scope = result.get("scope") or {}
|
||||||
|
if isinstance(scope, dict):
|
||||||
|
allow = scope.get("allowlist") or []
|
||||||
|
deny = scope.get("denylist") or []
|
||||||
|
|
||||||
|
if allow and isinstance(allow, list):
|
||||||
|
allow_str = ", ".join(_truncate(str(a), 40) for a in allow[:5])
|
||||||
|
text.append(f"\n allow: {allow_str}", style="dim")
|
||||||
|
if deny and isinstance(deny, list):
|
||||||
|
deny_str = ", ".join(_truncate(str(d), 40) for d in deny[:5])
|
||||||
|
text.append(f"\n deny: {deny_str}", style="dim")
|
||||||
|
|
||||||
|
elif "message" in result:
|
||||||
|
text.append(f" {result['message']}", style="#22c55e")
|
||||||
|
|
||||||
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(text, classes=css_classes)
|
return Static(text, classes=css_classes)
|
||||||
|
|
||||||
|
|
||||||
@@ -225,36 +467,81 @@ class ListSitemapRenderer(BaseToolRenderer):
|
|||||||
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def render(cls, tool_data: dict[str, Any]) -> Static:
|
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: PLR0912, PLR0915
|
||||||
|
args = tool_data.get("args", {})
|
||||||
result = tool_data.get("result")
|
result = tool_data.get("result")
|
||||||
|
status = tool_data.get("status", "running")
|
||||||
|
|
||||||
|
parent_id = args.get("parent_id")
|
||||||
|
scope_id = args.get("scope_id")
|
||||||
|
depth = args.get("depth")
|
||||||
|
|
||||||
text = Text()
|
text = Text()
|
||||||
text.append("🗺️ ")
|
text.append(PROXY_ICON, style="dim")
|
||||||
text.append("Listing sitemap", style="bold #06b6d4")
|
text.append(" listing sitemap", style="#06b6d4")
|
||||||
|
|
||||||
if isinstance(result, str) and result.strip():
|
if parent_id:
|
||||||
text.append("\n ")
|
text.append(f" under #{_truncate(str(parent_id), 20)}", style="dim")
|
||||||
text.append(result.strip(), style="dim")
|
|
||||||
elif result and isinstance(result, dict) and "entries" in result:
|
meta_parts = []
|
||||||
entries = result["entries"]
|
if scope_id and isinstance(scope_id, str):
|
||||||
if isinstance(entries, list) and entries:
|
meta_parts.append(f"scope:{scope_id[:8]}")
|
||||||
for entry in entries[:30]:
|
if depth and depth != "DIRECT":
|
||||||
if isinstance(entry, dict):
|
meta_parts.append(depth.lower())
|
||||||
label = entry.get("label", "?")
|
if meta_parts:
|
||||||
kind = entry.get("kind", "?")
|
text.append(f" ({', '.join(meta_parts)})", style="dim")
|
||||||
text.append("\n ")
|
|
||||||
text.append(f"{kind}: {label}", style="dim")
|
if status == "completed" and isinstance(result, dict):
|
||||||
if len(entries) > 30:
|
if "error" in result:
|
||||||
text.append("\n ")
|
text.append(f" error: {_sanitize(str(result['error']), 150)}", style="#ef4444")
|
||||||
text.append(f"... +{len(entries) - 30} more entries", style="dim")
|
|
||||||
else:
|
else:
|
||||||
text.append("\n ")
|
total = result.get("total_count", 0)
|
||||||
text.append("No entries found", style="dim")
|
entries = result.get("entries", [])
|
||||||
else:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append("Loading...", style="dim")
|
|
||||||
|
|
||||||
css_classes = cls.get_css_classes("completed")
|
text.append(f" [{total} entries]", style="dim")
|
||||||
|
|
||||||
|
if entries and isinstance(entries, list):
|
||||||
|
text.append("\n")
|
||||||
|
for i, entry in enumerate(entries[:MAX_REQUESTS_DISPLAY]):
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
continue
|
||||||
|
kind = entry.get("kind") or "?"
|
||||||
|
label = entry.get("label") or "?"
|
||||||
|
has_children = entry.get("hasDescendants", False)
|
||||||
|
req = entry.get("request") or {}
|
||||||
|
|
||||||
|
kind_style = {
|
||||||
|
"DOMAIN": "#f59e0b",
|
||||||
|
"DIRECTORY": "#3b82f6",
|
||||||
|
"REQUEST": "#22c55e",
|
||||||
|
}.get(kind, "dim")
|
||||||
|
|
||||||
|
text.append(" ")
|
||||||
|
kind_abbr = kind[:3] if isinstance(kind, str) else "?"
|
||||||
|
text.append(f"{kind_abbr:3}", style=kind_style)
|
||||||
|
text.append(f" {_truncate(label, 150)}", style="dim")
|
||||||
|
|
||||||
|
if req:
|
||||||
|
method = req.get("method", "")
|
||||||
|
code = req.get("status")
|
||||||
|
if method:
|
||||||
|
text.append(f" {method}", style="#a78bfa")
|
||||||
|
if code:
|
||||||
|
text.append(f" {code}", style=_status_style(code))
|
||||||
|
|
||||||
|
if has_children:
|
||||||
|
text.append(" +", style="dim italic")
|
||||||
|
|
||||||
|
if i < min(len(entries), MAX_REQUESTS_DISPLAY) - 1:
|
||||||
|
text.append("\n")
|
||||||
|
|
||||||
|
if len(entries) > MAX_REQUESTS_DISPLAY:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(
|
||||||
|
f" ... +{len(entries) - MAX_REQUESTS_DISPLAY} more", style="dim italic"
|
||||||
|
)
|
||||||
|
|
||||||
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(text, classes=css_classes)
|
return Static(text, classes=css_classes)
|
||||||
|
|
||||||
|
|
||||||
@@ -264,33 +551,60 @@ class ViewSitemapEntryRenderer(BaseToolRenderer):
|
|||||||
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def render(cls, tool_data: dict[str, Any]) -> Static:
|
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: PLR0912
|
||||||
|
args = tool_data.get("args", {})
|
||||||
result = tool_data.get("result")
|
result = tool_data.get("result")
|
||||||
|
status = tool_data.get("status", "running")
|
||||||
|
|
||||||
|
entry_id = args.get("entry_id", "")
|
||||||
|
|
||||||
text = Text()
|
text = Text()
|
||||||
text.append("📍 ")
|
text.append(PROXY_ICON, style="dim")
|
||||||
text.append("Viewing sitemap entry", style="bold #06b6d4")
|
text.append(" viewing sitemap", style="#06b6d4")
|
||||||
|
|
||||||
if isinstance(result, str) and result.strip():
|
if entry_id:
|
||||||
text.append("\n ")
|
text.append(f" #{_truncate(str(entry_id), 20)}", style="dim")
|
||||||
text.append(result.strip(), style="dim")
|
|
||||||
elif result and isinstance(result, dict) and "entry" in result:
|
if status == "completed" and isinstance(result, dict):
|
||||||
entry = result["entry"]
|
if "error" in result:
|
||||||
if isinstance(entry, dict):
|
text.append(f" error: {_sanitize(str(result['error']), 150)}", style="#ef4444")
|
||||||
label = entry.get("label", "")
|
elif "entry" in result:
|
||||||
|
entry = result.get("entry") or {}
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
entry = {}
|
||||||
kind = entry.get("kind", "")
|
kind = entry.get("kind", "")
|
||||||
if label and kind:
|
label = entry.get("label", "")
|
||||||
text.append("\n ")
|
related = entry.get("related_requests") or {}
|
||||||
text.append(f"{kind}: {label}", style="dim")
|
related_reqs = related.get("requests", []) if isinstance(related, dict) else []
|
||||||
else:
|
total_related = related.get("total_count", 0) if isinstance(related, dict) else 0
|
||||||
text.append("\n ")
|
|
||||||
text.append("Entry details loaded", style="dim")
|
|
||||||
else:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append("Entry details loaded", style="dim")
|
|
||||||
else:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append("Loading...", style="dim")
|
|
||||||
|
|
||||||
css_classes = cls.get_css_classes("completed")
|
if kind and label:
|
||||||
|
text.append(f" {kind}: {_truncate(label, 120)}", style="dim")
|
||||||
|
|
||||||
|
if total_related:
|
||||||
|
text.append(f" [{total_related} requests]", style="dim")
|
||||||
|
|
||||||
|
if related_reqs and isinstance(related_reqs, list):
|
||||||
|
text.append("\n")
|
||||||
|
for i, req in enumerate(related_reqs[:10]):
|
||||||
|
if not isinstance(req, dict):
|
||||||
|
continue
|
||||||
|
method = req.get("method", "?")
|
||||||
|
path = req.get("path", "/")
|
||||||
|
code = req.get("status")
|
||||||
|
|
||||||
|
text.append(" ")
|
||||||
|
text.append(f"{method:6}", style="#a78bfa")
|
||||||
|
text.append(f" {_truncate(path, 180)}", style="dim")
|
||||||
|
if code:
|
||||||
|
text.append(f" {code}", style=_status_style(code))
|
||||||
|
|
||||||
|
if i < min(len(related_reqs), 10) - 1:
|
||||||
|
text.append("\n")
|
||||||
|
|
||||||
|
if len(related_reqs) > 10:
|
||||||
|
text.append("\n")
|
||||||
|
text.append(f" ... +{len(related_reqs) - 10} more", style="dim italic")
|
||||||
|
|
||||||
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(text, classes=css_classes)
|
return Static(text, classes=css_classes)
|
||||||
|
|||||||
Reference in New Issue
Block a user