tsugite-cli 0.3.3__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.
- tsugite/__init__.py +6 -0
- tsugite/agent_composition.py +163 -0
- tsugite/agent_inheritance.py +479 -0
- tsugite/agent_preparation.py +236 -0
- tsugite/agent_runner/__init__.py +45 -0
- tsugite/agent_runner/helpers.py +106 -0
- tsugite/agent_runner/history_integration.py +248 -0
- tsugite/agent_runner/metrics.py +100 -0
- tsugite/agent_runner/runner.py +1879 -0
- tsugite/agent_runner/validation.py +70 -0
- tsugite/agent_utils.py +167 -0
- tsugite/attachments/__init__.py +65 -0
- tsugite/attachments/auto_context.py +199 -0
- tsugite/attachments/base.py +34 -0
- tsugite/attachments/file.py +51 -0
- tsugite/attachments/inline.py +31 -0
- tsugite/attachments/storage.py +178 -0
- tsugite/attachments/url.py +59 -0
- tsugite/attachments/youtube.py +101 -0
- tsugite/benchmark/__init__.py +62 -0
- tsugite/benchmark/config.py +183 -0
- tsugite/benchmark/core.py +292 -0
- tsugite/benchmark/discovery.py +377 -0
- tsugite/benchmark/evaluators.py +671 -0
- tsugite/benchmark/execution.py +657 -0
- tsugite/benchmark/metrics.py +204 -0
- tsugite/benchmark/reports.py +420 -0
- tsugite/benchmark/utils.py +288 -0
- tsugite/builtin_agents/chat-assistant.md +53 -0
- tsugite/builtin_agents/default.md +140 -0
- tsugite/builtin_agents.py +5 -0
- tsugite/cache.py +195 -0
- tsugite/cli/__init__.py +1042 -0
- tsugite/cli/agents.py +148 -0
- tsugite/cli/attachments.py +193 -0
- tsugite/cli/benchmark.py +663 -0
- tsugite/cli/cache.py +113 -0
- tsugite/cli/config.py +272 -0
- tsugite/cli/helpers.py +534 -0
- tsugite/cli/history.py +193 -0
- tsugite/cli/init.py +387 -0
- tsugite/cli/mcp.py +193 -0
- tsugite/cli/tools.py +419 -0
- tsugite/config.py +204 -0
- tsugite/console.py +48 -0
- tsugite/constants.py +21 -0
- tsugite/core/__init__.py +19 -0
- tsugite/core/agent.py +774 -0
- tsugite/core/executor.py +300 -0
- tsugite/core/memory.py +67 -0
- tsugite/core/tools.py +271 -0
- tsugite/docker_cli.py +270 -0
- tsugite/events/__init__.py +55 -0
- tsugite/events/base.py +46 -0
- tsugite/events/bus.py +62 -0
- tsugite/events/events.py +224 -0
- tsugite/exceptions.py +40 -0
- tsugite/history/__init__.py +29 -0
- tsugite/history/index.py +210 -0
- tsugite/history/models.py +106 -0
- tsugite/history/storage.py +157 -0
- tsugite/mcp_client.py +219 -0
- tsugite/mcp_config.py +174 -0
- tsugite/md_agents.py +751 -0
- tsugite/models.py +257 -0
- tsugite/renderer.py +151 -0
- tsugite/shell_tool_config.py +265 -0
- tsugite/templates/assistant.md +14 -0
- tsugite/tools/__init__.py +265 -0
- tsugite/tools/agents.py +312 -0
- tsugite/tools/edit_strategies.py +393 -0
- tsugite/tools/fs.py +329 -0
- tsugite/tools/http.py +239 -0
- tsugite/tools/interactive.py +430 -0
- tsugite/tools/shell.py +129 -0
- tsugite/tools/shell_tools.py +214 -0
- tsugite/tools/tasks.py +339 -0
- tsugite/tsugite.py +7 -0
- tsugite/ui/__init__.py +46 -0
- tsugite/ui/base.py +638 -0
- tsugite/ui/chat.py +265 -0
- tsugite/ui/chat.tcss +92 -0
- tsugite/ui/chat_history.py +286 -0
- tsugite/ui/helpers.py +102 -0
- tsugite/ui/jsonl.py +125 -0
- tsugite/ui/live_template.py +529 -0
- tsugite/ui/plain.py +419 -0
- tsugite/ui/textual_chat.py +642 -0
- tsugite/ui/textual_handler.py +225 -0
- tsugite/ui/widgets/__init__.py +6 -0
- tsugite/ui/widgets/base_scroll_log.py +27 -0
- tsugite/ui/widgets/message_list.py +121 -0
- tsugite/ui/widgets/thought_log.py +80 -0
- tsugite/ui_context.py +90 -0
- tsugite/utils.py +367 -0
- tsugite/xdg.py +104 -0
- tsugite_cli-0.3.3.dist-info/METADATA +325 -0
- tsugite_cli-0.3.3.dist-info/RECORD +101 -0
- tsugite_cli-0.3.3.dist-info/WHEEL +4 -0
- tsugite_cli-0.3.3.dist-info/entry_points.txt +5 -0
- tsugite_cli-0.3.3.dist-info/licenses/LICENSE +235 -0
tsugite/ui/helpers.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Helper functions for creating and managing UI loggers."""
|
|
2
|
+
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from typing import Generator
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from tsugite.ui.base import CustomUIHandler, CustomUILogger
|
|
9
|
+
from tsugite.ui.plain import PlainUIHandler
|
|
10
|
+
from tsugite.ui_context import clear_ui_context, set_ui_context
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@contextmanager
|
|
14
|
+
def custom_agent_ui(
|
|
15
|
+
console: Console,
|
|
16
|
+
show_code: bool = True,
|
|
17
|
+
show_observations: bool = True,
|
|
18
|
+
show_progress: bool = True,
|
|
19
|
+
show_llm_messages: bool = False,
|
|
20
|
+
show_execution_results: bool = True,
|
|
21
|
+
show_execution_logs: bool = True,
|
|
22
|
+
show_panels: bool = True,
|
|
23
|
+
show_debug_messages: bool = False,
|
|
24
|
+
) -> Generator[CustomUILogger, None, None]:
|
|
25
|
+
"""Context manager for custom agent UI.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
console: Rich console instance
|
|
29
|
+
show_code: Whether to display executed code
|
|
30
|
+
show_observations: Whether to display tool observations
|
|
31
|
+
show_progress: Whether to show progress spinner
|
|
32
|
+
show_llm_messages: Whether to show LLM reasoning messages
|
|
33
|
+
show_execution_results: Whether to show code execution results
|
|
34
|
+
show_execution_logs: Whether to show execution logs
|
|
35
|
+
show_panels: Whether to show Rich panels (borders and decorations)
|
|
36
|
+
show_debug_messages: Whether to show debug messages (requires --verbose)
|
|
37
|
+
|
|
38
|
+
Yields:
|
|
39
|
+
CustomUILogger: Logger instance with ui_handler and console
|
|
40
|
+
"""
|
|
41
|
+
ui_handler = CustomUIHandler(
|
|
42
|
+
console,
|
|
43
|
+
show_code=show_code,
|
|
44
|
+
show_observations=show_observations,
|
|
45
|
+
show_llm_messages=show_llm_messages,
|
|
46
|
+
show_execution_results=show_execution_results,
|
|
47
|
+
show_execution_logs=show_execution_logs,
|
|
48
|
+
show_panels=show_panels,
|
|
49
|
+
show_debug_messages=show_debug_messages,
|
|
50
|
+
)
|
|
51
|
+
logger = CustomUILogger(ui_handler, console)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
if show_progress:
|
|
55
|
+
# progress_context() will call set_ui_context() with the progress instance
|
|
56
|
+
with ui_handler.progress_context():
|
|
57
|
+
yield logger
|
|
58
|
+
else:
|
|
59
|
+
# No progress, so set ui_context here without progress
|
|
60
|
+
set_ui_context(console=console, progress=None, ui_handler=ui_handler)
|
|
61
|
+
yield logger
|
|
62
|
+
finally:
|
|
63
|
+
clear_ui_context()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def create_plain_logger() -> CustomUILogger:
|
|
67
|
+
"""Create a plain text logger for copy-paste friendly output.
|
|
68
|
+
|
|
69
|
+
Returns a logger using PlainUIHandler with no colors, panels, or emojis.
|
|
70
|
+
Ideal for:
|
|
71
|
+
- Piped output
|
|
72
|
+
- Copy-paste workflows
|
|
73
|
+
- Screen readers
|
|
74
|
+
- Logs and automation
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
CustomUILogger with PlainUIHandler
|
|
78
|
+
"""
|
|
79
|
+
plain_handler = PlainUIHandler()
|
|
80
|
+
return CustomUILogger(plain_handler, plain_handler.console)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def create_live_template_logger(interactive: bool = True) -> CustomUILogger:
|
|
84
|
+
"""Create logger using Live Template handler with Tree and interactive prompts.
|
|
85
|
+
|
|
86
|
+
Returns a logger using LiveTemplateHandler with Rich Live Display.
|
|
87
|
+
Features:
|
|
88
|
+
- Live Display with multi-panel layout
|
|
89
|
+
- Tree visualization of execution steps
|
|
90
|
+
- Interactive prompts (optional)
|
|
91
|
+
- Real-time updates without scrolling
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
interactive: Enable interactive prompts during execution
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
CustomUILogger with LiveTemplateHandler
|
|
98
|
+
"""
|
|
99
|
+
from tsugite.ui.live_template import LiveTemplateHandler
|
|
100
|
+
|
|
101
|
+
handler = LiveTemplateHandler(interactive=interactive)
|
|
102
|
+
return CustomUILogger(handler, handler.console)
|
tsugite/ui/jsonl.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""JSONL UI handler for subprocess-based subagent communication."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict
|
|
5
|
+
|
|
6
|
+
from tsugite.events import (
|
|
7
|
+
BaseEvent,
|
|
8
|
+
CodeExecutionEvent,
|
|
9
|
+
ErrorEvent,
|
|
10
|
+
ExecutionResultEvent,
|
|
11
|
+
FinalAnswerEvent,
|
|
12
|
+
LLMMessageEvent,
|
|
13
|
+
ObservationEvent,
|
|
14
|
+
StepStartEvent,
|
|
15
|
+
TaskStartEvent,
|
|
16
|
+
ToolCallEvent,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class JSONLUIHandler:
|
|
21
|
+
"""Emit UI events as JSONL to stdout for subprocess communication.
|
|
22
|
+
|
|
23
|
+
This handler converts all UI events to line-delimited JSON objects,
|
|
24
|
+
enabling parent agents to monitor subagent progress in real-time.
|
|
25
|
+
|
|
26
|
+
JSONL Protocol Schema:
|
|
27
|
+
----------------------
|
|
28
|
+
Each line is a JSON object with a "type" field and type-specific data.
|
|
29
|
+
|
|
30
|
+
Event Type Mappings:
|
|
31
|
+
- TaskStartEvent → {"type": "init", "agent": str, "model": str}
|
|
32
|
+
- StepStartEvent → {"type": "turn_start", "turn": int}
|
|
33
|
+
- LLMMessageEvent → {"type": "thought", "content": str}
|
|
34
|
+
- CodeExecutionEvent → {"type": "code", "content": str}
|
|
35
|
+
- ToolCallEvent → {"type": "tool_call", "tool": str, "args": dict}
|
|
36
|
+
- ObservationEvent → {"type": "tool_result", "tool": str, "success": bool, "output"?: str, "error"?: str}
|
|
37
|
+
- ExecutionResultEvent→ {"type": "tool_result", "tool": "code_execution", "success": bool, "output"?: str, "error"?: str}
|
|
38
|
+
- FinalAnswerEvent → {"type": "final_result", "result": str, "turns": int, "tokens": int, "cost": float}
|
|
39
|
+
- ErrorEvent → {"type": "error", "error": str, "step": int}
|
|
40
|
+
|
|
41
|
+
Success/Failure Patterns:
|
|
42
|
+
- Successful tool: {"type": "tool_result", "tool": "read_file", "success": true, "output": "..."}
|
|
43
|
+
- Failed tool: {"type": "tool_result", "tool": "read_file", "success": false, "error": "..."}
|
|
44
|
+
- Code execution success: {"type": "tool_result", "tool": "code_execution", "success": true, "output": "logs\\nresult"}
|
|
45
|
+
- Code execution failure: {"type": "tool_result", "tool": "code_execution", "success": false, "error": "..."}
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def handle_event(self, event: BaseEvent) -> None:
|
|
49
|
+
"""Convert UI event to JSONL and print to stdout.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
event: The Pydantic event
|
|
53
|
+
"""
|
|
54
|
+
if isinstance(event, TaskStartEvent):
|
|
55
|
+
self._emit("init", {"agent": event.task, "model": event.model})
|
|
56
|
+
|
|
57
|
+
elif isinstance(event, StepStartEvent):
|
|
58
|
+
self._emit("turn_start", {"turn": event.step})
|
|
59
|
+
|
|
60
|
+
elif isinstance(event, LLMMessageEvent):
|
|
61
|
+
if event.content.strip():
|
|
62
|
+
self._emit("thought", {"content": event.content})
|
|
63
|
+
|
|
64
|
+
elif isinstance(event, CodeExecutionEvent):
|
|
65
|
+
if event.code:
|
|
66
|
+
self._emit("code", {"content": event.code})
|
|
67
|
+
|
|
68
|
+
elif isinstance(event, ToolCallEvent):
|
|
69
|
+
self._emit("tool_call", {"tool": event.tool, "args": event.args})
|
|
70
|
+
|
|
71
|
+
elif isinstance(event, ObservationEvent):
|
|
72
|
+
if event.success:
|
|
73
|
+
self._emit(
|
|
74
|
+
"tool_result", {"tool": event.tool or "unknown", "success": True, "output": event.observation}
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
self._emit(
|
|
78
|
+
"tool_result",
|
|
79
|
+
{"tool": event.tool or "unknown", "success": False, "error": event.error or event.observation},
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
elif isinstance(event, ExecutionResultEvent):
|
|
83
|
+
if event.success:
|
|
84
|
+
# Combine logs and output for backward compatibility
|
|
85
|
+
output = event.output or ""
|
|
86
|
+
if event.logs:
|
|
87
|
+
logs_str = "\n".join(event.logs)
|
|
88
|
+
output = f"{logs_str}\n{output}" if output else logs_str
|
|
89
|
+
self._emit("tool_result", {"tool": "code_execution", "success": True, "output": output})
|
|
90
|
+
else:
|
|
91
|
+
self._emit("tool_result", {"tool": "code_execution", "success": False, "error": event.error or ""})
|
|
92
|
+
|
|
93
|
+
elif isinstance(event, FinalAnswerEvent):
|
|
94
|
+
self._emit(
|
|
95
|
+
"final_result",
|
|
96
|
+
{
|
|
97
|
+
"result": event.answer,
|
|
98
|
+
"turns": event.turns,
|
|
99
|
+
"tokens": event.tokens,
|
|
100
|
+
"cost": event.cost,
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
elif isinstance(event, ErrorEvent):
|
|
105
|
+
self._emit("error", {"error": event.error, "step": event.step})
|
|
106
|
+
|
|
107
|
+
def _emit(self, event_type: str, data: Dict[str, Any]) -> None:
|
|
108
|
+
"""Print JSONL event to stdout.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
event_type: The event type string
|
|
112
|
+
data: Event-specific data dictionary
|
|
113
|
+
"""
|
|
114
|
+
event = {"type": event_type, **data}
|
|
115
|
+
print(json.dumps(event), flush=True)
|
|
116
|
+
|
|
117
|
+
def update_progress(self, description: str) -> None:
|
|
118
|
+
"""No-op for progress updates in JSONL mode."""
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
def progress_context(self):
|
|
122
|
+
"""No-op context manager for compatibility."""
|
|
123
|
+
from contextlib import nullcontext
|
|
124
|
+
|
|
125
|
+
return nullcontext()
|