strix-agent 0.4.0__py3-none-any.whl → 0.6.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- strix/agents/StrixAgent/strix_agent.py +3 -3
- strix/agents/StrixAgent/system_prompt.jinja +30 -26
- strix/agents/base_agent.py +159 -75
- strix/agents/state.py +5 -2
- strix/config/__init__.py +12 -0
- strix/config/config.py +172 -0
- strix/interface/assets/tui_styles.tcss +195 -230
- strix/interface/cli.py +16 -41
- strix/interface/main.py +151 -74
- strix/interface/streaming_parser.py +119 -0
- strix/interface/tool_components/__init__.py +4 -0
- strix/interface/tool_components/agent_message_renderer.py +190 -0
- strix/interface/tool_components/agents_graph_renderer.py +54 -38
- strix/interface/tool_components/base_renderer.py +68 -36
- strix/interface/tool_components/browser_renderer.py +106 -91
- strix/interface/tool_components/file_edit_renderer.py +117 -36
- strix/interface/tool_components/finish_renderer.py +43 -10
- strix/interface/tool_components/notes_renderer.py +63 -38
- strix/interface/tool_components/proxy_renderer.py +133 -92
- strix/interface/tool_components/python_renderer.py +121 -8
- strix/interface/tool_components/registry.py +19 -12
- strix/interface/tool_components/reporting_renderer.py +196 -28
- strix/interface/tool_components/scan_info_renderer.py +22 -19
- strix/interface/tool_components/terminal_renderer.py +270 -90
- strix/interface/tool_components/thinking_renderer.py +8 -6
- strix/interface/tool_components/todo_renderer.py +225 -0
- strix/interface/tool_components/user_message_renderer.py +26 -19
- strix/interface/tool_components/web_search_renderer.py +7 -6
- strix/interface/tui.py +907 -262
- strix/interface/utils.py +236 -4
- strix/llm/__init__.py +6 -2
- strix/llm/config.py +8 -5
- strix/llm/dedupe.py +217 -0
- strix/llm/llm.py +209 -356
- strix/llm/memory_compressor.py +6 -5
- strix/llm/utils.py +17 -8
- strix/runtime/__init__.py +12 -3
- strix/runtime/docker_runtime.py +121 -202
- strix/runtime/tool_server.py +55 -95
- strix/skills/README.md +64 -0
- strix/skills/__init__.py +110 -0
- strix/{prompts → skills}/frameworks/nextjs.jinja +26 -0
- strix/skills/scan_modes/deep.jinja +145 -0
- strix/skills/scan_modes/quick.jinja +63 -0
- strix/skills/scan_modes/standard.jinja +91 -0
- strix/telemetry/README.md +38 -0
- strix/telemetry/__init__.py +7 -1
- strix/telemetry/posthog.py +137 -0
- strix/telemetry/tracer.py +194 -54
- strix/tools/__init__.py +11 -4
- strix/tools/agents_graph/agents_graph_actions.py +20 -21
- strix/tools/agents_graph/agents_graph_actions_schema.xml +8 -8
- strix/tools/browser/browser_actions.py +10 -6
- strix/tools/browser/browser_actions_schema.xml +6 -1
- strix/tools/browser/browser_instance.py +96 -48
- strix/tools/browser/tab_manager.py +121 -102
- strix/tools/context.py +12 -0
- strix/tools/executor.py +63 -4
- strix/tools/file_edit/file_edit_actions.py +6 -3
- strix/tools/file_edit/file_edit_actions_schema.xml +45 -3
- strix/tools/finish/finish_actions.py +80 -105
- strix/tools/finish/finish_actions_schema.xml +121 -14
- strix/tools/notes/notes_actions.py +6 -33
- strix/tools/notes/notes_actions_schema.xml +50 -46
- strix/tools/proxy/proxy_actions.py +14 -2
- strix/tools/proxy/proxy_actions_schema.xml +0 -1
- strix/tools/proxy/proxy_manager.py +28 -16
- strix/tools/python/python_actions.py +2 -2
- strix/tools/python/python_actions_schema.xml +9 -1
- strix/tools/python/python_instance.py +39 -37
- strix/tools/python/python_manager.py +43 -31
- strix/tools/registry.py +73 -12
- strix/tools/reporting/reporting_actions.py +218 -31
- strix/tools/reporting/reporting_actions_schema.xml +256 -8
- strix/tools/terminal/terminal_actions.py +2 -2
- strix/tools/terminal/terminal_actions_schema.xml +6 -0
- strix/tools/terminal/terminal_manager.py +41 -30
- strix/tools/thinking/thinking_actions_schema.xml +27 -25
- strix/tools/todo/__init__.py +18 -0
- strix/tools/todo/todo_actions.py +568 -0
- strix/tools/todo/todo_actions_schema.xml +225 -0
- strix/utils/__init__.py +0 -0
- strix/utils/resource_paths.py +13 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/METADATA +90 -65
- strix_agent-0.6.2.dist-info/RECORD +134 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/WHEEL +1 -1
- strix/llm/request_queue.py +0 -87
- strix/prompts/README.md +0 -64
- strix/prompts/__init__.py +0 -109
- strix_agent-0.4.0.dist-info/RECORD +0 -118
- /strix/{prompts → skills}/cloud/.gitkeep +0 -0
- /strix/{prompts → skills}/coordination/root_agent.jinja +0 -0
- /strix/{prompts → skills}/custom/.gitkeep +0 -0
- /strix/{prompts → skills}/frameworks/fastapi.jinja +0 -0
- /strix/{prompts → skills}/protocols/graphql.jinja +0 -0
- /strix/{prompts → skills}/reconnaissance/.gitkeep +0 -0
- /strix/{prompts → skills}/technologies/firebase_firestore.jinja +0 -0
- /strix/{prompts → skills}/technologies/supabase.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/authentication_jwt.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/broken_function_level_authorization.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/business_logic.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/csrf.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/idor.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/information_disclosure.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/insecure_file_uploads.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/mass_assignment.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/open_redirect.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/path_traversal_lfi_rfi.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/race_conditions.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/rce.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/sql_injection.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/ssrf.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/subdomain_takeover.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/xss.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/xxe.jinja +0 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/entry_points.txt +0 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
from functools import cache
|
|
2
|
+
from typing import Any, ClassVar
|
|
3
|
+
|
|
4
|
+
from pygments.lexers import get_lexer_by_name, guess_lexer
|
|
5
|
+
from pygments.styles import get_style_by_name
|
|
6
|
+
from pygments.util import ClassNotFound
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
from textual.widgets import Static
|
|
9
|
+
|
|
10
|
+
from .base_renderer import BaseToolRenderer
|
|
11
|
+
from .registry import register_tool_renderer
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_HEADER_STYLES = [
|
|
15
|
+
("###### ", 7, "bold #4ade80"),
|
|
16
|
+
("##### ", 6, "bold #22c55e"),
|
|
17
|
+
("#### ", 5, "bold #16a34a"),
|
|
18
|
+
("### ", 4, "bold #15803d"),
|
|
19
|
+
("## ", 3, "bold #22c55e"),
|
|
20
|
+
("# ", 2, "bold #4ade80"),
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@cache
|
|
25
|
+
def _get_style_colors() -> dict[Any, str]:
|
|
26
|
+
style = get_style_by_name("native")
|
|
27
|
+
return {token: f"#{style_def['color']}" for token, style_def in style if style_def["color"]}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_token_color(token_type: Any) -> str | None:
|
|
31
|
+
colors = _get_style_colors()
|
|
32
|
+
while token_type:
|
|
33
|
+
if token_type in colors:
|
|
34
|
+
return colors[token_type]
|
|
35
|
+
token_type = token_type.parent
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _highlight_code(code: str, language: str | None = None) -> Text:
|
|
40
|
+
text = Text()
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
lexer = get_lexer_by_name(language) if language else guess_lexer(code)
|
|
44
|
+
except ClassNotFound:
|
|
45
|
+
text.append(code, style="#d4d4d4")
|
|
46
|
+
return text
|
|
47
|
+
|
|
48
|
+
for token_type, token_value in lexer.get_tokens(code):
|
|
49
|
+
if not token_value:
|
|
50
|
+
continue
|
|
51
|
+
color = _get_token_color(token_type)
|
|
52
|
+
text.append(token_value, style=color)
|
|
53
|
+
|
|
54
|
+
return text
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _try_parse_header(line: str) -> tuple[str, str] | None:
|
|
58
|
+
for prefix, strip_len, style in _HEADER_STYLES:
|
|
59
|
+
if line.startswith(prefix):
|
|
60
|
+
return (line[strip_len:], style)
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _apply_markdown_styles(text: str) -> Text: # noqa: PLR0912
|
|
65
|
+
result = Text()
|
|
66
|
+
lines = text.split("\n")
|
|
67
|
+
|
|
68
|
+
in_code_block = False
|
|
69
|
+
code_block_lang: str | None = None
|
|
70
|
+
code_block_lines: list[str] = []
|
|
71
|
+
|
|
72
|
+
for i, line in enumerate(lines):
|
|
73
|
+
if i > 0 and not in_code_block:
|
|
74
|
+
result.append("\n")
|
|
75
|
+
|
|
76
|
+
if line.startswith("```"):
|
|
77
|
+
if not in_code_block:
|
|
78
|
+
in_code_block = True
|
|
79
|
+
code_block_lang = line[3:].strip() or None
|
|
80
|
+
code_block_lines = []
|
|
81
|
+
if i > 0:
|
|
82
|
+
result.append("\n")
|
|
83
|
+
else:
|
|
84
|
+
in_code_block = False
|
|
85
|
+
code_content = "\n".join(code_block_lines)
|
|
86
|
+
if code_content:
|
|
87
|
+
result.append_text(_highlight_code(code_content, code_block_lang))
|
|
88
|
+
code_block_lines = []
|
|
89
|
+
code_block_lang = None
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
if in_code_block:
|
|
93
|
+
code_block_lines.append(line)
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
header = _try_parse_header(line)
|
|
97
|
+
if header:
|
|
98
|
+
result.append(header[0], style=header[1])
|
|
99
|
+
elif line.startswith("> "):
|
|
100
|
+
result.append("┃ ", style="#22c55e")
|
|
101
|
+
result.append_text(_process_inline_formatting(line[2:]))
|
|
102
|
+
elif line.startswith(("- ", "* ")):
|
|
103
|
+
result.append("• ", style="#22c55e")
|
|
104
|
+
result.append_text(_process_inline_formatting(line[2:]))
|
|
105
|
+
elif len(line) > 2 and line[0].isdigit() and line[1:3] in (". ", ") "):
|
|
106
|
+
result.append(line[0] + ". ", style="#22c55e")
|
|
107
|
+
result.append_text(_process_inline_formatting(line[2:]))
|
|
108
|
+
elif line.strip() in ("---", "***", "___"):
|
|
109
|
+
result.append("─" * 40, style="#22c55e")
|
|
110
|
+
else:
|
|
111
|
+
result.append_text(_process_inline_formatting(line))
|
|
112
|
+
|
|
113
|
+
if in_code_block and code_block_lines:
|
|
114
|
+
code_content = "\n".join(code_block_lines)
|
|
115
|
+
result.append_text(_highlight_code(code_content, code_block_lang))
|
|
116
|
+
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _process_inline_formatting(line: str) -> Text:
|
|
121
|
+
result = Text()
|
|
122
|
+
i = 0
|
|
123
|
+
n = len(line)
|
|
124
|
+
|
|
125
|
+
while i < n:
|
|
126
|
+
if i + 1 < n and line[i : i + 2] in ("**", "__"):
|
|
127
|
+
marker = line[i : i + 2]
|
|
128
|
+
end = line.find(marker, i + 2)
|
|
129
|
+
if end != -1:
|
|
130
|
+
result.append(line[i + 2 : end], style="bold #4ade80")
|
|
131
|
+
i = end + 2
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
if i + 1 < n and line[i : i + 2] == "~~":
|
|
135
|
+
end = line.find("~~", i + 2)
|
|
136
|
+
if end != -1:
|
|
137
|
+
result.append(line[i + 2 : end], style="strike #525252")
|
|
138
|
+
i = end + 2
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
if line[i] == "`":
|
|
142
|
+
end = line.find("`", i + 1)
|
|
143
|
+
if end != -1:
|
|
144
|
+
result.append(line[i + 1 : end], style="bold #22c55e on #0a0a0a")
|
|
145
|
+
i = end + 1
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
if line[i] in ("*", "_"):
|
|
149
|
+
marker = line[i]
|
|
150
|
+
if i + 1 < n and line[i + 1] != marker:
|
|
151
|
+
end = line.find(marker, i + 1)
|
|
152
|
+
if end != -1 and (end + 1 >= n or line[end + 1] != marker):
|
|
153
|
+
result.append(line[i + 1 : end], style="italic #86efac")
|
|
154
|
+
i = end + 1
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
result.append(line[i])
|
|
158
|
+
i += 1
|
|
159
|
+
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@register_tool_renderer
|
|
164
|
+
class AgentMessageRenderer(BaseToolRenderer):
|
|
165
|
+
tool_name: ClassVar[str] = "agent_message"
|
|
166
|
+
css_classes: ClassVar[list[str]] = ["chat-message", "agent-message"]
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
170
|
+
content = tool_data.get("content", "")
|
|
171
|
+
|
|
172
|
+
if not content:
|
|
173
|
+
return Static(Text(), classes=" ".join(cls.css_classes))
|
|
174
|
+
|
|
175
|
+
styled_text = _apply_markdown_styles(content)
|
|
176
|
+
|
|
177
|
+
return Static(styled_text, classes=" ".join(cls.css_classes))
|
|
178
|
+
|
|
179
|
+
@classmethod
|
|
180
|
+
def render_simple(cls, content: str) -> Text:
|
|
181
|
+
if not content:
|
|
182
|
+
return Text()
|
|
183
|
+
|
|
184
|
+
from strix.llm.utils import clean_content
|
|
185
|
+
|
|
186
|
+
cleaned = clean_content(content)
|
|
187
|
+
if not cleaned:
|
|
188
|
+
return Text()
|
|
189
|
+
|
|
190
|
+
return _apply_markdown_styles(cleaned)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Any, ClassVar
|
|
2
2
|
|
|
3
|
+
from rich.text import Text
|
|
3
4
|
from textual.widgets import Static
|
|
4
5
|
|
|
5
6
|
from .base_renderer import BaseToolRenderer
|
|
@@ -12,11 +13,15 @@ class ViewAgentGraphRenderer(BaseToolRenderer):
|
|
|
12
13
|
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
|
13
14
|
|
|
14
15
|
@classmethod
|
|
15
|
-
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
16
|
-
|
|
16
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
17
|
+
status = tool_data.get("status", "unknown")
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
text = Text()
|
|
20
|
+
text.append("◇ ", style="#a78bfa")
|
|
21
|
+
text.append("viewing agents graph", style="dim")
|
|
22
|
+
|
|
23
|
+
css_classes = cls.get_css_classes(status)
|
|
24
|
+
return Static(text, classes=css_classes)
|
|
20
25
|
|
|
21
26
|
|
|
22
27
|
@register_tool_renderer
|
|
@@ -27,20 +32,22 @@ class CreateAgentRenderer(BaseToolRenderer):
|
|
|
27
32
|
@classmethod
|
|
28
33
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
29
34
|
args = tool_data.get("args", {})
|
|
35
|
+
status = tool_data.get("status", "unknown")
|
|
30
36
|
|
|
31
37
|
task = args.get("task", "")
|
|
32
38
|
name = args.get("name", "Agent")
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
text = Text()
|
|
41
|
+
text.append("◈ ", style="#a78bfa")
|
|
42
|
+
text.append("spawning ", style="dim")
|
|
43
|
+
text.append(name, style="bold #a78bfa")
|
|
35
44
|
|
|
36
45
|
if task:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
else:
|
|
40
|
-
content_text = f"{header}\n [dim]Spawning agent...[/]"
|
|
46
|
+
text.append("\n ")
|
|
47
|
+
text.append(task, style="dim")
|
|
41
48
|
|
|
42
|
-
css_classes = cls.get_css_classes(
|
|
43
|
-
return Static(
|
|
49
|
+
css_classes = cls.get_css_classes(status)
|
|
50
|
+
return Static(text, classes=css_classes)
|
|
44
51
|
|
|
45
52
|
|
|
46
53
|
@register_tool_renderer
|
|
@@ -51,19 +58,24 @@ class SendMessageToAgentRenderer(BaseToolRenderer):
|
|
|
51
58
|
@classmethod
|
|
52
59
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
53
60
|
args = tool_data.get("args", {})
|
|
61
|
+
status = tool_data.get("status", "unknown")
|
|
54
62
|
|
|
55
63
|
message = args.get("message", "")
|
|
64
|
+
agent_id = args.get("agent_id", "")
|
|
56
65
|
|
|
57
|
-
|
|
66
|
+
text = Text()
|
|
67
|
+
text.append("→ ", style="#60a5fa")
|
|
68
|
+
if agent_id:
|
|
69
|
+
text.append(f"to {agent_id}", style="dim")
|
|
70
|
+
else:
|
|
71
|
+
text.append("sending message", style="dim")
|
|
58
72
|
|
|
59
73
|
if message:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
else:
|
|
63
|
-
content_text = f"{header}\n [dim]Sending...[/]"
|
|
74
|
+
text.append("\n ")
|
|
75
|
+
text.append(message, style="dim")
|
|
64
76
|
|
|
65
|
-
css_classes = cls.get_css_classes(
|
|
66
|
-
return Static(
|
|
77
|
+
css_classes = cls.get_css_classes(status)
|
|
78
|
+
return Static(text, classes=css_classes)
|
|
67
79
|
|
|
68
80
|
|
|
69
81
|
@register_tool_renderer
|
|
@@ -79,25 +91,28 @@ class AgentFinishRenderer(BaseToolRenderer):
|
|
|
79
91
|
findings = args.get("findings", [])
|
|
80
92
|
success = args.get("success", True)
|
|
81
93
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
text = Text()
|
|
95
|
+
text.append("🏁 ")
|
|
96
|
+
|
|
97
|
+
if success:
|
|
98
|
+
text.append("Agent completed", style="bold #fbbf24")
|
|
99
|
+
else:
|
|
100
|
+
text.append("Agent failed", style="bold #fbbf24")
|
|
85
101
|
|
|
86
102
|
if result_summary:
|
|
87
|
-
|
|
103
|
+
text.append("\n ")
|
|
104
|
+
text.append(result_summary, style="bold")
|
|
88
105
|
|
|
89
106
|
if findings and isinstance(findings, list):
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
content_text = "\n".join(content_parts)
|
|
107
|
+
for finding in findings:
|
|
108
|
+
text.append("\n • ")
|
|
109
|
+
text.append(str(finding), style="dim")
|
|
96
110
|
else:
|
|
97
|
-
|
|
111
|
+
text.append("\n ")
|
|
112
|
+
text.append("Completing task...", style="dim")
|
|
98
113
|
|
|
99
114
|
css_classes = cls.get_css_classes("completed")
|
|
100
|
-
return Static(
|
|
115
|
+
return Static(text, classes=css_classes)
|
|
101
116
|
|
|
102
117
|
|
|
103
118
|
@register_tool_renderer
|
|
@@ -108,16 +123,17 @@ class WaitForMessageRenderer(BaseToolRenderer):
|
|
|
108
123
|
@classmethod
|
|
109
124
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
110
125
|
args = tool_data.get("args", {})
|
|
126
|
+
status = tool_data.get("status", "unknown")
|
|
111
127
|
|
|
112
|
-
reason = args.get("reason", "
|
|
128
|
+
reason = args.get("reason", "")
|
|
113
129
|
|
|
114
|
-
|
|
130
|
+
text = Text()
|
|
131
|
+
text.append("○ ", style="#6b7280")
|
|
132
|
+
text.append("waiting", style="dim")
|
|
115
133
|
|
|
116
134
|
if reason:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
else:
|
|
120
|
-
content_text = f"{header}\n [dim]Agent paused until message received...[/]"
|
|
135
|
+
text.append("\n ")
|
|
136
|
+
text.append(reason, style="dim")
|
|
121
137
|
|
|
122
|
-
css_classes = cls.get_css_classes(
|
|
123
|
-
return Static(
|
|
138
|
+
css_classes = cls.get_css_classes(status)
|
|
139
|
+
return Static(text, classes=css_classes)
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Any, ClassVar
|
|
2
|
+
from typing import Any, ClassVar
|
|
3
3
|
|
|
4
|
-
from rich.
|
|
4
|
+
from rich.text import Text
|
|
5
5
|
from textual.widgets import Static
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class BaseToolRenderer(ABC):
|
|
9
9
|
tool_name: ClassVar[str] = ""
|
|
10
|
-
|
|
11
10
|
css_classes: ClassVar[list[str]] = ["tool-call"]
|
|
12
11
|
|
|
13
12
|
@classmethod
|
|
@@ -16,47 +15,80 @@ class BaseToolRenderer(ABC):
|
|
|
16
15
|
pass
|
|
17
16
|
|
|
18
17
|
@classmethod
|
|
19
|
-
def
|
|
20
|
-
return
|
|
18
|
+
def build_text(cls, tool_data: dict[str, Any]) -> Text: # noqa: ARG003
|
|
19
|
+
return Text()
|
|
21
20
|
|
|
22
21
|
@classmethod
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
args_parts = []
|
|
28
|
-
for k, v in args.items():
|
|
29
|
-
str_v = str(v)
|
|
30
|
-
if len(str_v) > max_length:
|
|
31
|
-
str_v = str_v[: max_length - 3] + "..."
|
|
32
|
-
args_parts.append(f" [dim]{k}:[/] {cls.escape_markup(str_v)}")
|
|
33
|
-
return "\n".join(args_parts)
|
|
22
|
+
def create_static(cls, content: Text, status: str) -> Static:
|
|
23
|
+
css_classes = cls.get_css_classes(status)
|
|
24
|
+
return Static(content, classes=css_classes)
|
|
34
25
|
|
|
35
26
|
@classmethod
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return ""
|
|
43
|
-
|
|
44
|
-
if len(str_result) > max_length:
|
|
45
|
-
str_result = str_result[: max_length - 3] + "..."
|
|
46
|
-
return cls.escape_markup(str_result)
|
|
47
|
-
|
|
48
|
-
@classmethod
|
|
49
|
-
def get_status_icon(cls, status: str) -> str:
|
|
50
|
-
status_icons = {
|
|
51
|
-
"running": "[#f59e0b]●[/#f59e0b] In progress...",
|
|
52
|
-
"completed": "[#22c55e]✓[/#22c55e] Done",
|
|
53
|
-
"failed": "[#dc2626]✗[/#dc2626] Failed",
|
|
54
|
-
"error": "[#dc2626]✗[/#dc2626] Error",
|
|
27
|
+
def status_icon(cls, status: str) -> tuple[str, str]:
|
|
28
|
+
icons = {
|
|
29
|
+
"running": ("● In progress...", "#f59e0b"),
|
|
30
|
+
"completed": ("✓ Done", "#22c55e"),
|
|
31
|
+
"failed": ("✗ Failed", "#dc2626"),
|
|
32
|
+
"error": ("✗ Error", "#dc2626"),
|
|
55
33
|
}
|
|
56
|
-
return
|
|
34
|
+
return icons.get(status, ("○ Unknown", "dim"))
|
|
57
35
|
|
|
58
36
|
@classmethod
|
|
59
37
|
def get_css_classes(cls, status: str) -> str:
|
|
60
38
|
base_classes = cls.css_classes.copy()
|
|
61
39
|
base_classes.append(f"status-{status}")
|
|
62
40
|
return " ".join(base_classes)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def text_with_style(cls, content: str, style: str | None = None) -> Text:
|
|
44
|
+
text = Text()
|
|
45
|
+
text.append(content, style=style)
|
|
46
|
+
return text
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def text_icon_label(
|
|
50
|
+
cls,
|
|
51
|
+
icon: str,
|
|
52
|
+
label: str,
|
|
53
|
+
icon_style: str | None = None,
|
|
54
|
+
label_style: str | None = None,
|
|
55
|
+
) -> Text:
|
|
56
|
+
text = Text()
|
|
57
|
+
text.append(icon, style=icon_style)
|
|
58
|
+
text.append(" ")
|
|
59
|
+
text.append(label, style=label_style)
|
|
60
|
+
return text
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def text_header(
|
|
64
|
+
cls,
|
|
65
|
+
icon: str,
|
|
66
|
+
title: str,
|
|
67
|
+
subtitle: str = "",
|
|
68
|
+
title_style: str = "bold",
|
|
69
|
+
subtitle_style: str = "dim",
|
|
70
|
+
) -> Text:
|
|
71
|
+
text = Text()
|
|
72
|
+
text.append(icon)
|
|
73
|
+
text.append(" ")
|
|
74
|
+
text.append(title, style=title_style)
|
|
75
|
+
if subtitle:
|
|
76
|
+
text.append(" ")
|
|
77
|
+
text.append(subtitle, style=subtitle_style)
|
|
78
|
+
return text
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def text_key_value(
|
|
82
|
+
cls,
|
|
83
|
+
key: str,
|
|
84
|
+
value: str,
|
|
85
|
+
key_style: str = "dim",
|
|
86
|
+
value_style: str | None = None,
|
|
87
|
+
indent: int = 2,
|
|
88
|
+
) -> Text:
|
|
89
|
+
text = Text()
|
|
90
|
+
text.append(" " * indent)
|
|
91
|
+
text.append(key, style=key_style)
|
|
92
|
+
text.append(": ")
|
|
93
|
+
text.append(value, style=value_style)
|
|
94
|
+
return text
|