strix-agent 0.4.0__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/__init__.py +0 -0
- strix/agents/StrixAgent/__init__.py +4 -0
- strix/agents/StrixAgent/strix_agent.py +89 -0
- strix/agents/StrixAgent/system_prompt.jinja +404 -0
- strix/agents/__init__.py +10 -0
- strix/agents/base_agent.py +518 -0
- strix/agents/state.py +163 -0
- strix/interface/__init__.py +4 -0
- strix/interface/assets/tui_styles.tcss +694 -0
- strix/interface/cli.py +230 -0
- strix/interface/main.py +500 -0
- strix/interface/tool_components/__init__.py +39 -0
- strix/interface/tool_components/agents_graph_renderer.py +123 -0
- strix/interface/tool_components/base_renderer.py +62 -0
- strix/interface/tool_components/browser_renderer.py +120 -0
- strix/interface/tool_components/file_edit_renderer.py +99 -0
- strix/interface/tool_components/finish_renderer.py +31 -0
- strix/interface/tool_components/notes_renderer.py +108 -0
- strix/interface/tool_components/proxy_renderer.py +255 -0
- strix/interface/tool_components/python_renderer.py +34 -0
- strix/interface/tool_components/registry.py +72 -0
- strix/interface/tool_components/reporting_renderer.py +53 -0
- strix/interface/tool_components/scan_info_renderer.py +64 -0
- strix/interface/tool_components/terminal_renderer.py +131 -0
- strix/interface/tool_components/thinking_renderer.py +29 -0
- strix/interface/tool_components/user_message_renderer.py +43 -0
- strix/interface/tool_components/web_search_renderer.py +28 -0
- strix/interface/tui.py +1274 -0
- strix/interface/utils.py +559 -0
- strix/llm/__init__.py +15 -0
- strix/llm/config.py +20 -0
- strix/llm/llm.py +465 -0
- strix/llm/memory_compressor.py +212 -0
- strix/llm/request_queue.py +87 -0
- strix/llm/utils.py +87 -0
- strix/prompts/README.md +64 -0
- strix/prompts/__init__.py +109 -0
- strix/prompts/cloud/.gitkeep +0 -0
- strix/prompts/coordination/root_agent.jinja +41 -0
- strix/prompts/custom/.gitkeep +0 -0
- strix/prompts/frameworks/fastapi.jinja +142 -0
- strix/prompts/frameworks/nextjs.jinja +126 -0
- strix/prompts/protocols/graphql.jinja +215 -0
- strix/prompts/reconnaissance/.gitkeep +0 -0
- strix/prompts/technologies/firebase_firestore.jinja +177 -0
- strix/prompts/technologies/supabase.jinja +189 -0
- strix/prompts/vulnerabilities/authentication_jwt.jinja +147 -0
- strix/prompts/vulnerabilities/broken_function_level_authorization.jinja +146 -0
- strix/prompts/vulnerabilities/business_logic.jinja +171 -0
- strix/prompts/vulnerabilities/csrf.jinja +174 -0
- strix/prompts/vulnerabilities/idor.jinja +195 -0
- strix/prompts/vulnerabilities/information_disclosure.jinja +222 -0
- strix/prompts/vulnerabilities/insecure_file_uploads.jinja +188 -0
- strix/prompts/vulnerabilities/mass_assignment.jinja +141 -0
- strix/prompts/vulnerabilities/open_redirect.jinja +177 -0
- strix/prompts/vulnerabilities/path_traversal_lfi_rfi.jinja +142 -0
- strix/prompts/vulnerabilities/race_conditions.jinja +164 -0
- strix/prompts/vulnerabilities/rce.jinja +154 -0
- strix/prompts/vulnerabilities/sql_injection.jinja +151 -0
- strix/prompts/vulnerabilities/ssrf.jinja +135 -0
- strix/prompts/vulnerabilities/subdomain_takeover.jinja +155 -0
- strix/prompts/vulnerabilities/xss.jinja +169 -0
- strix/prompts/vulnerabilities/xxe.jinja +184 -0
- strix/runtime/__init__.py +19 -0
- strix/runtime/docker_runtime.py +399 -0
- strix/runtime/runtime.py +29 -0
- strix/runtime/tool_server.py +205 -0
- strix/telemetry/__init__.py +4 -0
- strix/telemetry/tracer.py +337 -0
- strix/tools/__init__.py +64 -0
- strix/tools/agents_graph/__init__.py +16 -0
- strix/tools/agents_graph/agents_graph_actions.py +621 -0
- strix/tools/agents_graph/agents_graph_actions_schema.xml +226 -0
- strix/tools/argument_parser.py +121 -0
- strix/tools/browser/__init__.py +4 -0
- strix/tools/browser/browser_actions.py +236 -0
- strix/tools/browser/browser_actions_schema.xml +183 -0
- strix/tools/browser/browser_instance.py +533 -0
- strix/tools/browser/tab_manager.py +342 -0
- strix/tools/executor.py +305 -0
- strix/tools/file_edit/__init__.py +4 -0
- strix/tools/file_edit/file_edit_actions.py +141 -0
- strix/tools/file_edit/file_edit_actions_schema.xml +128 -0
- strix/tools/finish/__init__.py +4 -0
- strix/tools/finish/finish_actions.py +174 -0
- strix/tools/finish/finish_actions_schema.xml +45 -0
- strix/tools/notes/__init__.py +14 -0
- strix/tools/notes/notes_actions.py +191 -0
- strix/tools/notes/notes_actions_schema.xml +150 -0
- strix/tools/proxy/__init__.py +20 -0
- strix/tools/proxy/proxy_actions.py +101 -0
- strix/tools/proxy/proxy_actions_schema.xml +267 -0
- strix/tools/proxy/proxy_manager.py +785 -0
- strix/tools/python/__init__.py +4 -0
- strix/tools/python/python_actions.py +47 -0
- strix/tools/python/python_actions_schema.xml +131 -0
- strix/tools/python/python_instance.py +172 -0
- strix/tools/python/python_manager.py +131 -0
- strix/tools/registry.py +196 -0
- strix/tools/reporting/__init__.py +6 -0
- strix/tools/reporting/reporting_actions.py +63 -0
- strix/tools/reporting/reporting_actions_schema.xml +30 -0
- strix/tools/terminal/__init__.py +4 -0
- strix/tools/terminal/terminal_actions.py +35 -0
- strix/tools/terminal/terminal_actions_schema.xml +146 -0
- strix/tools/terminal/terminal_manager.py +151 -0
- strix/tools/terminal/terminal_session.py +447 -0
- strix/tools/thinking/__init__.py +4 -0
- strix/tools/thinking/thinking_actions.py +18 -0
- strix/tools/thinking/thinking_actions_schema.xml +52 -0
- strix/tools/web_search/__init__.py +4 -0
- strix/tools/web_search/web_search_actions.py +80 -0
- strix/tools/web_search/web_search_actions_schema.xml +83 -0
- strix_agent-0.4.0.dist-info/LICENSE +201 -0
- strix_agent-0.4.0.dist-info/METADATA +282 -0
- strix_agent-0.4.0.dist-info/RECORD +118 -0
- strix_agent-0.4.0.dist-info/WHEEL +4 -0
- strix_agent-0.4.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import Any, ClassVar
|
|
2
|
+
|
|
3
|
+
from textual.widgets import Static
|
|
4
|
+
|
|
5
|
+
from .base_renderer import BaseToolRenderer
|
|
6
|
+
from .registry import register_tool_renderer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@register_tool_renderer
|
|
10
|
+
class PythonRenderer(BaseToolRenderer):
|
|
11
|
+
tool_name: ClassVar[str] = "python_action"
|
|
12
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "python-tool"]
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
16
|
+
args = tool_data.get("args", {})
|
|
17
|
+
|
|
18
|
+
action = args.get("action", "")
|
|
19
|
+
code = args.get("code", "")
|
|
20
|
+
|
|
21
|
+
header = "</> [bold #3b82f6]Python[/]"
|
|
22
|
+
|
|
23
|
+
if code and action in ["new_session", "execute"]:
|
|
24
|
+
code_display = code[:600] + "..." if len(code) > 600 else code
|
|
25
|
+
content_text = f"{header}\n [italic white]{cls.escape_markup(code_display)}[/]"
|
|
26
|
+
elif action == "close":
|
|
27
|
+
content_text = f"{header}\n [dim]Closing session...[/]"
|
|
28
|
+
elif action == "list_sessions":
|
|
29
|
+
content_text = f"{header}\n [dim]Listing sessions...[/]"
|
|
30
|
+
else:
|
|
31
|
+
content_text = f"{header}\n [dim]Running...[/]"
|
|
32
|
+
|
|
33
|
+
css_classes = cls.get_css_classes("completed")
|
|
34
|
+
return Static(content_text, classes=css_classes)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from typing import Any, ClassVar
|
|
2
|
+
|
|
3
|
+
from textual.widgets import Static
|
|
4
|
+
|
|
5
|
+
from .base_renderer import BaseToolRenderer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ToolTUIRegistry:
|
|
9
|
+
_renderers: ClassVar[dict[str, type[BaseToolRenderer]]] = {}
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def register(cls, renderer_class: type[BaseToolRenderer]) -> None:
|
|
13
|
+
if not renderer_class.tool_name:
|
|
14
|
+
raise ValueError(f"Renderer {renderer_class.__name__} must define tool_name")
|
|
15
|
+
|
|
16
|
+
cls._renderers[renderer_class.tool_name] = renderer_class
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def get_renderer(cls, tool_name: str) -> type[BaseToolRenderer] | None:
|
|
20
|
+
return cls._renderers.get(tool_name)
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def list_tools(cls) -> list[str]:
|
|
24
|
+
return list(cls._renderers.keys())
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def has_renderer(cls, tool_name: str) -> bool:
|
|
28
|
+
return tool_name in cls._renderers
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def register_tool_renderer(renderer_class: type[BaseToolRenderer]) -> type[BaseToolRenderer]:
|
|
32
|
+
ToolTUIRegistry.register(renderer_class)
|
|
33
|
+
return renderer_class
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_tool_renderer(tool_name: str) -> type[BaseToolRenderer] | None:
|
|
37
|
+
return ToolTUIRegistry.get_renderer(tool_name)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def render_tool_widget(tool_data: dict[str, Any]) -> Static:
|
|
41
|
+
tool_name = tool_data.get("tool_name", "")
|
|
42
|
+
renderer = get_tool_renderer(tool_name)
|
|
43
|
+
|
|
44
|
+
if renderer:
|
|
45
|
+
return renderer.render(tool_data)
|
|
46
|
+
return _render_default_tool_widget(tool_data)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _render_default_tool_widget(tool_data: dict[str, Any]) -> Static:
|
|
50
|
+
tool_name = BaseToolRenderer.escape_markup(tool_data.get("tool_name", "Unknown Tool"))
|
|
51
|
+
args = tool_data.get("args", {})
|
|
52
|
+
status = tool_data.get("status", "unknown")
|
|
53
|
+
result = tool_data.get("result")
|
|
54
|
+
|
|
55
|
+
status_text = BaseToolRenderer.get_status_icon(status)
|
|
56
|
+
|
|
57
|
+
header = f"→ Using tool [bold blue]{BaseToolRenderer.escape_markup(tool_name)}[/]"
|
|
58
|
+
content_parts = [header]
|
|
59
|
+
|
|
60
|
+
args_str = BaseToolRenderer.format_args(args)
|
|
61
|
+
if args_str:
|
|
62
|
+
content_parts.append(args_str)
|
|
63
|
+
|
|
64
|
+
if status in ["completed", "failed", "error"] and result is not None:
|
|
65
|
+
result_str = BaseToolRenderer.format_result(result)
|
|
66
|
+
if result_str:
|
|
67
|
+
content_parts.append(f"[bold]Result:[/] {result_str}")
|
|
68
|
+
else:
|
|
69
|
+
content_parts.append(status_text)
|
|
70
|
+
|
|
71
|
+
css_classes = BaseToolRenderer.get_css_classes(status)
|
|
72
|
+
return Static("\n".join(content_parts), classes=css_classes)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import Any, ClassVar
|
|
2
|
+
|
|
3
|
+
from textual.widgets import Static
|
|
4
|
+
|
|
5
|
+
from .base_renderer import BaseToolRenderer
|
|
6
|
+
from .registry import register_tool_renderer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@register_tool_renderer
|
|
10
|
+
class CreateVulnerabilityReportRenderer(BaseToolRenderer):
|
|
11
|
+
tool_name: ClassVar[str] = "create_vulnerability_report"
|
|
12
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "reporting-tool"]
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
16
|
+
args = tool_data.get("args", {})
|
|
17
|
+
|
|
18
|
+
title = args.get("title", "")
|
|
19
|
+
severity = args.get("severity", "")
|
|
20
|
+
content = args.get("content", "")
|
|
21
|
+
|
|
22
|
+
header = "🐞 [bold #ea580c]Vulnerability Report[/]"
|
|
23
|
+
|
|
24
|
+
if title:
|
|
25
|
+
content_parts = [f"{header}\n [bold]{cls.escape_markup(title)}[/]"]
|
|
26
|
+
|
|
27
|
+
if severity:
|
|
28
|
+
severity_color = cls._get_severity_color(severity.lower())
|
|
29
|
+
content_parts.append(
|
|
30
|
+
f" [dim]Severity: [{severity_color}]"
|
|
31
|
+
f"{cls.escape_markup(severity.upper())}[/{severity_color}][/]"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if content:
|
|
35
|
+
content_parts.append(f" [dim]{cls.escape_markup(content)}[/]")
|
|
36
|
+
|
|
37
|
+
content_text = "\n".join(content_parts)
|
|
38
|
+
else:
|
|
39
|
+
content_text = f"{header}\n [dim]Creating report...[/]"
|
|
40
|
+
|
|
41
|
+
css_classes = cls.get_css_classes("completed")
|
|
42
|
+
return Static(content_text, classes=css_classes)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def _get_severity_color(cls, severity: str) -> str:
|
|
46
|
+
severity_colors = {
|
|
47
|
+
"critical": "#dc2626",
|
|
48
|
+
"high": "#ea580c",
|
|
49
|
+
"medium": "#d97706",
|
|
50
|
+
"low": "#65a30d",
|
|
51
|
+
"info": "#0284c7",
|
|
52
|
+
}
|
|
53
|
+
return severity_colors.get(severity, "#6b7280")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import Any, ClassVar
|
|
2
|
+
|
|
3
|
+
from textual.widgets import Static
|
|
4
|
+
|
|
5
|
+
from .base_renderer import BaseToolRenderer
|
|
6
|
+
from .registry import register_tool_renderer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@register_tool_renderer
|
|
10
|
+
class ScanStartInfoRenderer(BaseToolRenderer):
|
|
11
|
+
tool_name: ClassVar[str] = "scan_start_info"
|
|
12
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "scan-info-tool"]
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
16
|
+
args = tool_data.get("args", {})
|
|
17
|
+
status = tool_data.get("status", "unknown")
|
|
18
|
+
|
|
19
|
+
targets = args.get("targets", [])
|
|
20
|
+
|
|
21
|
+
if len(targets) == 1:
|
|
22
|
+
target_display = cls._build_single_target_display(targets[0])
|
|
23
|
+
content = f"🚀 Starting penetration test on {target_display}"
|
|
24
|
+
elif len(targets) > 1:
|
|
25
|
+
content = f"🚀 Starting penetration test on {len(targets)} targets"
|
|
26
|
+
for target_info in targets:
|
|
27
|
+
target_display = cls._build_single_target_display(target_info)
|
|
28
|
+
content += f"\n • {target_display}"
|
|
29
|
+
else:
|
|
30
|
+
content = "🚀 Starting penetration test"
|
|
31
|
+
|
|
32
|
+
css_classes = cls.get_css_classes(status)
|
|
33
|
+
return Static(content, classes=css_classes)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def _build_single_target_display(cls, target_info: dict[str, Any]) -> str:
|
|
37
|
+
original = target_info.get("original")
|
|
38
|
+
if original:
|
|
39
|
+
return cls.escape_markup(str(original))
|
|
40
|
+
|
|
41
|
+
return "unknown target"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@register_tool_renderer
|
|
45
|
+
class SubagentStartInfoRenderer(BaseToolRenderer):
|
|
46
|
+
tool_name: ClassVar[str] = "subagent_start_info"
|
|
47
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "subagent-info-tool"]
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
51
|
+
args = tool_data.get("args", {})
|
|
52
|
+
status = tool_data.get("status", "unknown")
|
|
53
|
+
|
|
54
|
+
name = args.get("name", "Unknown Agent")
|
|
55
|
+
task = args.get("task", "")
|
|
56
|
+
|
|
57
|
+
name = cls.escape_markup(str(name))
|
|
58
|
+
content = f"🤖 Spawned subagent {name}"
|
|
59
|
+
if task:
|
|
60
|
+
task = cls.escape_markup(str(task))
|
|
61
|
+
content += f"\n Task: {task}"
|
|
62
|
+
|
|
63
|
+
css_classes = cls.get_css_classes(status)
|
|
64
|
+
return Static(content, classes=css_classes)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from typing import Any, ClassVar
|
|
2
|
+
|
|
3
|
+
from textual.widgets import Static
|
|
4
|
+
|
|
5
|
+
from .base_renderer import BaseToolRenderer
|
|
6
|
+
from .registry import register_tool_renderer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@register_tool_renderer
|
|
10
|
+
class TerminalRenderer(BaseToolRenderer):
|
|
11
|
+
tool_name: ClassVar[str] = "terminal_execute"
|
|
12
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "terminal-tool"]
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
16
|
+
args = tool_data.get("args", {})
|
|
17
|
+
status = tool_data.get("status", "unknown")
|
|
18
|
+
result = tool_data.get("result", {})
|
|
19
|
+
|
|
20
|
+
command = args.get("command", "")
|
|
21
|
+
is_input = args.get("is_input", False)
|
|
22
|
+
terminal_id = args.get("terminal_id", "default")
|
|
23
|
+
timeout = args.get("timeout")
|
|
24
|
+
|
|
25
|
+
content = cls._build_sleek_content(command, is_input, terminal_id, timeout, result)
|
|
26
|
+
|
|
27
|
+
css_classes = cls.get_css_classes(status)
|
|
28
|
+
return Static(content, classes=css_classes)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def _build_sleek_content(
|
|
32
|
+
cls,
|
|
33
|
+
command: str,
|
|
34
|
+
is_input: bool,
|
|
35
|
+
terminal_id: str, # noqa: ARG003
|
|
36
|
+
timeout: float | None, # noqa: ARG003
|
|
37
|
+
result: dict[str, Any], # noqa: ARG003
|
|
38
|
+
) -> str:
|
|
39
|
+
terminal_icon = ">_"
|
|
40
|
+
|
|
41
|
+
if not command.strip():
|
|
42
|
+
return f"{terminal_icon} [dim]getting logs...[/]"
|
|
43
|
+
|
|
44
|
+
control_sequences = {
|
|
45
|
+
"C-c",
|
|
46
|
+
"C-d",
|
|
47
|
+
"C-z",
|
|
48
|
+
"C-a",
|
|
49
|
+
"C-e",
|
|
50
|
+
"C-k",
|
|
51
|
+
"C-l",
|
|
52
|
+
"C-u",
|
|
53
|
+
"C-w",
|
|
54
|
+
"C-r",
|
|
55
|
+
"C-s",
|
|
56
|
+
"C-t",
|
|
57
|
+
"C-y",
|
|
58
|
+
"^c",
|
|
59
|
+
"^d",
|
|
60
|
+
"^z",
|
|
61
|
+
"^a",
|
|
62
|
+
"^e",
|
|
63
|
+
"^k",
|
|
64
|
+
"^l",
|
|
65
|
+
"^u",
|
|
66
|
+
"^w",
|
|
67
|
+
"^r",
|
|
68
|
+
"^s",
|
|
69
|
+
"^t",
|
|
70
|
+
"^y",
|
|
71
|
+
}
|
|
72
|
+
special_keys = {
|
|
73
|
+
"Enter",
|
|
74
|
+
"Escape",
|
|
75
|
+
"Space",
|
|
76
|
+
"Tab",
|
|
77
|
+
"BTab",
|
|
78
|
+
"BSpace",
|
|
79
|
+
"DC",
|
|
80
|
+
"IC",
|
|
81
|
+
"Up",
|
|
82
|
+
"Down",
|
|
83
|
+
"Left",
|
|
84
|
+
"Right",
|
|
85
|
+
"Home",
|
|
86
|
+
"End",
|
|
87
|
+
"PageUp",
|
|
88
|
+
"PageDown",
|
|
89
|
+
"PgUp",
|
|
90
|
+
"PgDn",
|
|
91
|
+
"PPage",
|
|
92
|
+
"NPage",
|
|
93
|
+
"F1",
|
|
94
|
+
"F2",
|
|
95
|
+
"F3",
|
|
96
|
+
"F4",
|
|
97
|
+
"F5",
|
|
98
|
+
"F6",
|
|
99
|
+
"F7",
|
|
100
|
+
"F8",
|
|
101
|
+
"F9",
|
|
102
|
+
"F10",
|
|
103
|
+
"F11",
|
|
104
|
+
"F12",
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
is_special = (
|
|
108
|
+
command in control_sequences
|
|
109
|
+
or command in special_keys
|
|
110
|
+
or command.startswith(("M-", "S-", "C-S-", "C-M-", "S-M-"))
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if is_special:
|
|
114
|
+
return f"{terminal_icon} [#ef4444]{cls.escape_markup(command)}[/]"
|
|
115
|
+
|
|
116
|
+
if is_input:
|
|
117
|
+
formatted_command = cls._format_command_display(command)
|
|
118
|
+
return f"{terminal_icon} [#3b82f6]>>>[/] [#22c55e]{formatted_command}[/]"
|
|
119
|
+
|
|
120
|
+
formatted_command = cls._format_command_display(command)
|
|
121
|
+
return f"{terminal_icon} [#22c55e]$ {formatted_command}[/]"
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def _format_command_display(cls, command: str) -> str:
|
|
125
|
+
if not command:
|
|
126
|
+
return ""
|
|
127
|
+
|
|
128
|
+
if len(command) > 400:
|
|
129
|
+
command = command[:397] + "..."
|
|
130
|
+
|
|
131
|
+
return cls.escape_markup(command)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Any, ClassVar
|
|
2
|
+
|
|
3
|
+
from textual.widgets import Static
|
|
4
|
+
|
|
5
|
+
from .base_renderer import BaseToolRenderer
|
|
6
|
+
from .registry import register_tool_renderer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@register_tool_renderer
|
|
10
|
+
class ThinkRenderer(BaseToolRenderer):
|
|
11
|
+
tool_name: ClassVar[str] = "think"
|
|
12
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "thinking-tool"]
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
16
|
+
args = tool_data.get("args", {})
|
|
17
|
+
|
|
18
|
+
thought = args.get("thought", "")
|
|
19
|
+
|
|
20
|
+
header = "🧠 [bold #a855f7]Thinking[/]"
|
|
21
|
+
|
|
22
|
+
if thought:
|
|
23
|
+
thought_display = thought[:600] + "..." if len(thought) > 600 else thought
|
|
24
|
+
content = f"{header}\n [italic dim]{cls.escape_markup(thought_display)}[/]"
|
|
25
|
+
else:
|
|
26
|
+
content = f"{header}\n [italic dim]Thinking...[/]"
|
|
27
|
+
|
|
28
|
+
css_classes = cls.get_css_classes("completed")
|
|
29
|
+
return Static(content, classes=css_classes)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import Any, ClassVar
|
|
2
|
+
|
|
3
|
+
from textual.widgets import Static
|
|
4
|
+
|
|
5
|
+
from .base_renderer import BaseToolRenderer
|
|
6
|
+
from .registry import register_tool_renderer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@register_tool_renderer
|
|
10
|
+
class UserMessageRenderer(BaseToolRenderer):
|
|
11
|
+
tool_name: ClassVar[str] = "user_message"
|
|
12
|
+
css_classes: ClassVar[list[str]] = ["chat-message", "user-message"]
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def render(cls, message_data: dict[str, Any]) -> Static:
|
|
16
|
+
content = message_data.get("content", "")
|
|
17
|
+
|
|
18
|
+
if not content:
|
|
19
|
+
return Static("", classes=cls.css_classes)
|
|
20
|
+
|
|
21
|
+
if len(content) > 300:
|
|
22
|
+
content = content[:297] + "..."
|
|
23
|
+
|
|
24
|
+
lines = content.split("\n")
|
|
25
|
+
bordered_lines = [f"[#3b82f6]▍[/#3b82f6] {line}" for line in lines]
|
|
26
|
+
bordered_content = "\n".join(bordered_lines)
|
|
27
|
+
formatted_content = f"[#3b82f6]▍[/#3b82f6] [bold]You:[/]\n{bordered_content}"
|
|
28
|
+
|
|
29
|
+
css_classes = " ".join(cls.css_classes)
|
|
30
|
+
return Static(formatted_content, classes=css_classes)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def render_simple(cls, content: str) -> str:
|
|
34
|
+
if not content:
|
|
35
|
+
return ""
|
|
36
|
+
|
|
37
|
+
if len(content) > 300:
|
|
38
|
+
content = content[:297] + "..."
|
|
39
|
+
|
|
40
|
+
lines = content.split("\n")
|
|
41
|
+
bordered_lines = [f"[#3b82f6]▍[/#3b82f6] {line}" for line in lines]
|
|
42
|
+
bordered_content = "\n".join(bordered_lines)
|
|
43
|
+
return f"[#3b82f6]▍[/#3b82f6] [bold]You:[/]\n{bordered_content}"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from typing import Any, ClassVar
|
|
2
|
+
|
|
3
|
+
from textual.widgets import Static
|
|
4
|
+
|
|
5
|
+
from .base_renderer import BaseToolRenderer
|
|
6
|
+
from .registry import register_tool_renderer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@register_tool_renderer
|
|
10
|
+
class WebSearchRenderer(BaseToolRenderer):
|
|
11
|
+
tool_name: ClassVar[str] = "web_search"
|
|
12
|
+
css_classes: ClassVar[list[str]] = ["tool-call", "web-search-tool"]
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
16
|
+
args = tool_data.get("args", {})
|
|
17
|
+
query = args.get("query", "")
|
|
18
|
+
|
|
19
|
+
header = "🌐 [bold #60a5fa]Searching the web...[/]"
|
|
20
|
+
|
|
21
|
+
if query:
|
|
22
|
+
query_display = query[:100] + "..." if len(query) > 100 else query
|
|
23
|
+
content_text = f"{header}\n [dim]{cls.escape_markup(query_display)}[/]"
|
|
24
|
+
else:
|
|
25
|
+
content_text = f"{header}"
|
|
26
|
+
|
|
27
|
+
css_classes = cls.get_css_classes("completed")
|
|
28
|
+
return Static(content_text, classes=css_classes)
|