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
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
"""Live UI handler template using Rich Live Display, Tree, and Prompt.
|
|
2
|
+
|
|
3
|
+
This is a TEMPLATE showing how to create a custom UI mode with:
|
|
4
|
+
- Live Display for dynamic updates
|
|
5
|
+
- Tree for showing execution hierarchy
|
|
6
|
+
- Prompt for interactive elements
|
|
7
|
+
|
|
8
|
+
To use this template:
|
|
9
|
+
1. Rename this file (e.g., live_interactive.py)
|
|
10
|
+
2. Rename the class (e.g., LiveInteractiveHandler)
|
|
11
|
+
3. Customize the rendering methods for your needs
|
|
12
|
+
4. Add helper function in ui/helpers.py
|
|
13
|
+
5. Export in ui/__init__.py
|
|
14
|
+
6. Integrate in cli/__init__.py
|
|
15
|
+
|
|
16
|
+
See CLAUDE.md for full UI system documentation.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from contextlib import contextmanager
|
|
20
|
+
from typing import Generator, Optional
|
|
21
|
+
|
|
22
|
+
from rich.console import Console
|
|
23
|
+
from rich.layout import Layout
|
|
24
|
+
from rich.live import Live
|
|
25
|
+
from rich.panel import Panel
|
|
26
|
+
from rich.prompt import Confirm, Prompt
|
|
27
|
+
from rich.table import Table
|
|
28
|
+
from rich.tree import Tree
|
|
29
|
+
|
|
30
|
+
from tsugite.events import (
|
|
31
|
+
CodeExecutionEvent,
|
|
32
|
+
CostSummaryEvent,
|
|
33
|
+
ErrorEvent,
|
|
34
|
+
FinalAnswerEvent,
|
|
35
|
+
ObservationEvent,
|
|
36
|
+
ReasoningContentEvent,
|
|
37
|
+
StepStartEvent,
|
|
38
|
+
TaskStartEvent,
|
|
39
|
+
ToolCallEvent,
|
|
40
|
+
)
|
|
41
|
+
from tsugite.ui.base import CustomUIHandler
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class LiveTemplateHandler(CustomUIHandler):
|
|
45
|
+
"""Template UI handler using Rich Live Display with Tree and interactive prompts.
|
|
46
|
+
|
|
47
|
+
Features demonstrated:
|
|
48
|
+
- Live Display: Real-time updates without scrolling
|
|
49
|
+
- Tree: Hierarchical view of execution steps
|
|
50
|
+
- Prompt: Interactive user input during execution
|
|
51
|
+
- Layout: Multi-panel interface
|
|
52
|
+
|
|
53
|
+
Customize this to fit your needs!
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, console: Console = None, interactive: bool = True):
|
|
57
|
+
"""Initialize the live template handler.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
console: Rich console instance (creates stderr console if None)
|
|
61
|
+
interactive: Whether to enable interactive prompts
|
|
62
|
+
"""
|
|
63
|
+
if console is None:
|
|
64
|
+
from tsugite.console import get_stderr_console
|
|
65
|
+
|
|
66
|
+
console = get_stderr_console()
|
|
67
|
+
|
|
68
|
+
# Initialize parent with your preferred flags
|
|
69
|
+
super().__init__(
|
|
70
|
+
console=console,
|
|
71
|
+
show_code=True, # Customize these
|
|
72
|
+
show_observations=True,
|
|
73
|
+
show_llm_messages=False,
|
|
74
|
+
show_execution_results=True,
|
|
75
|
+
show_execution_logs=True,
|
|
76
|
+
show_panels=False, # We'll use Live Display instead
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Live display components
|
|
80
|
+
self.live_display: Optional[Live] = None
|
|
81
|
+
self.execution_tree: Optional[Tree] = None
|
|
82
|
+
self.interactive = interactive
|
|
83
|
+
|
|
84
|
+
# Track current state for live updates
|
|
85
|
+
self.current_status = "Initializing..."
|
|
86
|
+
self.step_count = 0
|
|
87
|
+
self.tool_calls = []
|
|
88
|
+
self.errors = []
|
|
89
|
+
|
|
90
|
+
@contextmanager
|
|
91
|
+
def progress_context(self) -> Generator[None, None, None]:
|
|
92
|
+
"""Context manager for Live Display during execution.
|
|
93
|
+
|
|
94
|
+
This replaces the default progress spinner with a full Live Display.
|
|
95
|
+
"""
|
|
96
|
+
from tsugite.ui_context import clear_ui_context, set_ui_context
|
|
97
|
+
|
|
98
|
+
# Create the layout
|
|
99
|
+
layout = self._create_layout()
|
|
100
|
+
|
|
101
|
+
# Create Live Display
|
|
102
|
+
self.live_display = Live(
|
|
103
|
+
layout,
|
|
104
|
+
console=self.console,
|
|
105
|
+
refresh_per_second=4, # Adjust refresh rate
|
|
106
|
+
screen=False, # Set to True for full-screen mode
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Store console and ui_handler in thread-local for tool access
|
|
110
|
+
set_ui_context(console=self.console, progress=None, ui_handler=self)
|
|
111
|
+
|
|
112
|
+
with self.live_display:
|
|
113
|
+
try:
|
|
114
|
+
yield
|
|
115
|
+
finally:
|
|
116
|
+
self.live_display = None
|
|
117
|
+
clear_ui_context()
|
|
118
|
+
|
|
119
|
+
@contextmanager
|
|
120
|
+
def pause_for_input(self) -> Generator[None, None, None]:
|
|
121
|
+
"""Pause the Live Display for user input.
|
|
122
|
+
|
|
123
|
+
Stops the live display, shows the prompt, then restarts it.
|
|
124
|
+
"""
|
|
125
|
+
if self.live_display is not None:
|
|
126
|
+
self.live_display.stop()
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
yield
|
|
130
|
+
finally:
|
|
131
|
+
if self.live_display is not None:
|
|
132
|
+
self.live_display.start()
|
|
133
|
+
|
|
134
|
+
def _create_layout(self) -> Layout:
|
|
135
|
+
"""Create the layout structure for Live Display.
|
|
136
|
+
|
|
137
|
+
Customize this to arrange your panels however you want.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Layout with configured panels
|
|
141
|
+
"""
|
|
142
|
+
# Create main layout with multiple sections
|
|
143
|
+
layout = Layout()
|
|
144
|
+
|
|
145
|
+
# Split into header, body, footer
|
|
146
|
+
layout.split(
|
|
147
|
+
Layout(name="header", size=3),
|
|
148
|
+
Layout(name="body"),
|
|
149
|
+
Layout(name="footer", size=3),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Split body into left (tree) and right (details)
|
|
153
|
+
layout["body"].split_row(
|
|
154
|
+
Layout(name="tree", ratio=1),
|
|
155
|
+
Layout(name="details", ratio=2),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Initial content
|
|
159
|
+
layout["header"].update(self._render_header())
|
|
160
|
+
layout["tree"].update(self._render_tree())
|
|
161
|
+
layout["details"].update(self._render_details())
|
|
162
|
+
layout["footer"].update(self._render_footer())
|
|
163
|
+
|
|
164
|
+
return layout
|
|
165
|
+
|
|
166
|
+
def _render_header(self) -> Panel:
|
|
167
|
+
"""Render the header panel.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Panel with header content
|
|
171
|
+
"""
|
|
172
|
+
return Panel(
|
|
173
|
+
f"[bold cyan]Tsugite Agent Execution[/bold cyan] | Status: {self.current_status}",
|
|
174
|
+
style="cyan",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def _render_tree(self) -> Panel:
|
|
178
|
+
"""Render the execution tree.
|
|
179
|
+
|
|
180
|
+
This shows the hierarchical structure of execution steps.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Panel with tree content
|
|
184
|
+
"""
|
|
185
|
+
if self.execution_tree is None:
|
|
186
|
+
# Create initial tree
|
|
187
|
+
self.execution_tree = Tree("🚀 [bold]Execution", guide_style="dim")
|
|
188
|
+
|
|
189
|
+
return Panel(self.execution_tree, title="[bold]Execution Tree[/bold]", border_style="green")
|
|
190
|
+
|
|
191
|
+
def _render_details(self) -> Panel:
|
|
192
|
+
"""Render the details panel.
|
|
193
|
+
|
|
194
|
+
Shows current step information, code, observations, etc.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Panel with detail content
|
|
198
|
+
"""
|
|
199
|
+
# Build details from current state
|
|
200
|
+
details = []
|
|
201
|
+
|
|
202
|
+
if self.state.task:
|
|
203
|
+
details.append(f"[bold]Task:[/bold] {self.state.task}")
|
|
204
|
+
details.append("")
|
|
205
|
+
|
|
206
|
+
if self.state.current_step:
|
|
207
|
+
details.append(f"[bold]Step:[/bold] {self.state.current_step}")
|
|
208
|
+
|
|
209
|
+
if self.state.code_being_executed:
|
|
210
|
+
details.append("\n[bold yellow]Executing Code:[/bold yellow]")
|
|
211
|
+
# Truncate long code
|
|
212
|
+
code_preview = self.state.code_being_executed[:200]
|
|
213
|
+
if len(self.state.code_being_executed) > 200:
|
|
214
|
+
code_preview += "..."
|
|
215
|
+
details.append(f"[dim]{code_preview}[/dim]")
|
|
216
|
+
|
|
217
|
+
# Show recent tool calls
|
|
218
|
+
if self.tool_calls:
|
|
219
|
+
details.append("\n[bold]Recent Tools:[/bold]")
|
|
220
|
+
for tool in self.tool_calls[-3:]: # Last 3 tools
|
|
221
|
+
details.append(f" • {tool}")
|
|
222
|
+
|
|
223
|
+
# Show errors if any
|
|
224
|
+
if self.errors:
|
|
225
|
+
details.append("\n[bold red]Errors:[/bold red]")
|
|
226
|
+
for error in self.errors[-2:]: # Last 2 errors
|
|
227
|
+
details.append(f" ⚠️ {error}")
|
|
228
|
+
|
|
229
|
+
content = "\n".join(details) if details else "[dim]No details yet...[/dim]"
|
|
230
|
+
return Panel(content, title="[bold]Details[/bold]", border_style="blue")
|
|
231
|
+
|
|
232
|
+
def _render_footer(self) -> Panel:
|
|
233
|
+
"""Render the footer panel.
|
|
234
|
+
|
|
235
|
+
Shows summary statistics, costs, etc.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Panel with footer content
|
|
239
|
+
"""
|
|
240
|
+
stats = f"Steps: {self.step_count} | Tools: {len(self.tool_calls)} | Errors: {len(self.errors)}"
|
|
241
|
+
return Panel(stats, style="dim")
|
|
242
|
+
|
|
243
|
+
def _update_live_display(self):
|
|
244
|
+
"""Update the live display with current state.
|
|
245
|
+
|
|
246
|
+
Call this whenever you want to refresh the display.
|
|
247
|
+
"""
|
|
248
|
+
if self.live_display and self.live_display.is_started:
|
|
249
|
+
layout = self._create_layout()
|
|
250
|
+
self.live_display.update(layout)
|
|
251
|
+
|
|
252
|
+
# ============================================================================
|
|
253
|
+
# Event Handlers - Override these to customize behavior
|
|
254
|
+
# ============================================================================
|
|
255
|
+
|
|
256
|
+
def _handle_task_start(self, event: TaskStartEvent) -> None:
|
|
257
|
+
"""Handle task start event."""
|
|
258
|
+
self.state.task = event.task
|
|
259
|
+
self.current_status = "Starting..."
|
|
260
|
+
|
|
261
|
+
# Create root of execution tree
|
|
262
|
+
self.execution_tree = Tree(
|
|
263
|
+
(
|
|
264
|
+
f"🚀 [bold]{self.state.task[:50]}...[/bold]"
|
|
265
|
+
if len(self.state.task or "") > 50
|
|
266
|
+
else f"🚀 [bold]{self.state.task}[/bold]"
|
|
267
|
+
),
|
|
268
|
+
guide_style="dim",
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
self._update_live_display()
|
|
272
|
+
|
|
273
|
+
def _handle_step_start(self, event: StepStartEvent) -> None:
|
|
274
|
+
"""Handle step start event."""
|
|
275
|
+
self.state.current_step = event.step
|
|
276
|
+
self.step_count = self.state.current_step
|
|
277
|
+
self.current_status = f"Step {self.state.current_step}: Thinking..."
|
|
278
|
+
|
|
279
|
+
# Add step to tree
|
|
280
|
+
if self.execution_tree:
|
|
281
|
+
step_label = (
|
|
282
|
+
f"Round {self.state.current_step}"
|
|
283
|
+
if self.state.multistep_context
|
|
284
|
+
else f"Step {self.state.current_step}"
|
|
285
|
+
)
|
|
286
|
+
self.execution_tree.add(f"🤔 {step_label}", style="cyan")
|
|
287
|
+
|
|
288
|
+
self._update_live_display()
|
|
289
|
+
|
|
290
|
+
def _handle_code_execution(self, event: CodeExecutionEvent) -> None:
|
|
291
|
+
"""Handle code execution event."""
|
|
292
|
+
self.state.code_being_executed = event.code
|
|
293
|
+
self.current_status = f"Step {self.state.current_step}: Executing code..."
|
|
294
|
+
|
|
295
|
+
# Add code execution to tree
|
|
296
|
+
if self.execution_tree and self.execution_tree.children:
|
|
297
|
+
last_step = self.execution_tree.children[-1]
|
|
298
|
+
code_preview = (
|
|
299
|
+
self.state.code_being_executed[:40] + "..."
|
|
300
|
+
if len(self.state.code_being_executed) > 40
|
|
301
|
+
else self.state.code_being_executed
|
|
302
|
+
)
|
|
303
|
+
last_step.add(f"⚡ Code: {code_preview}", style="yellow")
|
|
304
|
+
|
|
305
|
+
self._update_live_display()
|
|
306
|
+
|
|
307
|
+
def _handle_tool_call(self, event: ToolCallEvent) -> None:
|
|
308
|
+
"""Handle tool call event."""
|
|
309
|
+
content = event.tool
|
|
310
|
+
|
|
311
|
+
# Extract tool name from content
|
|
312
|
+
if "Calling tool:" in content:
|
|
313
|
+
tool_name = content.split("Calling tool:")[1].split("with")[0].strip()
|
|
314
|
+
self.tool_calls.append(tool_name)
|
|
315
|
+
self.current_status = f"Step {self.state.current_step}: Using {tool_name}..."
|
|
316
|
+
|
|
317
|
+
# Add to tree
|
|
318
|
+
if self.execution_tree and self.execution_tree.children:
|
|
319
|
+
last_step = self.execution_tree.children[-1]
|
|
320
|
+
last_step.add(f"🔧 {tool_name}", style="magenta")
|
|
321
|
+
|
|
322
|
+
self._update_live_display()
|
|
323
|
+
|
|
324
|
+
def _handle_observation(self, event: ObservationEvent) -> None:
|
|
325
|
+
"""Handle observation event."""
|
|
326
|
+
observation = event.observation
|
|
327
|
+
self.current_status = f"Step {self.state.current_step}: Processing results..."
|
|
328
|
+
|
|
329
|
+
# Check for errors in observation
|
|
330
|
+
is_error = any(keyword in observation.lower() for keyword in ["error", "failed", "exception", "traceback"])
|
|
331
|
+
|
|
332
|
+
if is_error:
|
|
333
|
+
self.errors.append(observation[:100])
|
|
334
|
+
# Add error to tree
|
|
335
|
+
if self.execution_tree and self.execution_tree.children:
|
|
336
|
+
last_step = self.execution_tree.children[-1]
|
|
337
|
+
last_step.add("❌ Error", style="red")
|
|
338
|
+
else:
|
|
339
|
+
# Add success to tree
|
|
340
|
+
if self.execution_tree and self.execution_tree.children:
|
|
341
|
+
last_step = self.execution_tree.children[-1]
|
|
342
|
+
obs_preview = observation[:40] + "..." if len(observation) > 40 else observation
|
|
343
|
+
last_step.add(f"✓ {obs_preview}", style="green")
|
|
344
|
+
|
|
345
|
+
self._update_live_display()
|
|
346
|
+
|
|
347
|
+
def _handle_final_answer(self, event: FinalAnswerEvent) -> None:
|
|
348
|
+
"""Handle final answer event."""
|
|
349
|
+
answer = str(event.answer)
|
|
350
|
+
self.current_status = "✅ Complete"
|
|
351
|
+
|
|
352
|
+
# Add final answer to tree
|
|
353
|
+
if self.execution_tree:
|
|
354
|
+
self.execution_tree.add(f"✅ [bold green]Final Answer: {answer[:50]}...[/bold green]")
|
|
355
|
+
|
|
356
|
+
self._update_live_display()
|
|
357
|
+
|
|
358
|
+
# Optional: Show interactive confirmation
|
|
359
|
+
if self.interactive:
|
|
360
|
+
self.live_display.stop()
|
|
361
|
+
self.console.print("\n" + "=" * 60)
|
|
362
|
+
self.console.print(f"[bold green]Final Answer:[/bold green] {answer}")
|
|
363
|
+
self.console.print("=" * 60)
|
|
364
|
+
|
|
365
|
+
# Example of using Prompt
|
|
366
|
+
# satisfied = Confirm.ask("Are you satisfied with this answer?")
|
|
367
|
+
# if not satisfied:
|
|
368
|
+
# # Could trigger retry or refinement
|
|
369
|
+
# pass
|
|
370
|
+
|
|
371
|
+
def _handle_error(self, event: ErrorEvent) -> None:
|
|
372
|
+
"""Handle error event."""
|
|
373
|
+
error = event.error
|
|
374
|
+
error_type = event.error_type or "Error"
|
|
375
|
+
|
|
376
|
+
self.errors.append(f"{error_type}: {error[:100]}")
|
|
377
|
+
self.current_status = f"❌ {error_type}"
|
|
378
|
+
|
|
379
|
+
# Add error to tree
|
|
380
|
+
if self.execution_tree:
|
|
381
|
+
self.execution_tree.add(f"❌ [bold red]{error_type}[/bold red]", style="red")
|
|
382
|
+
|
|
383
|
+
self._update_live_display()
|
|
384
|
+
|
|
385
|
+
def _handle_reasoning_content(self, event: ReasoningContentEvent) -> None:
|
|
386
|
+
"""Handle reasoning content from models like Claude."""
|
|
387
|
+
content = event.content
|
|
388
|
+
|
|
389
|
+
# Add reasoning to tree
|
|
390
|
+
if self.execution_tree and content:
|
|
391
|
+
reasoning_preview = content[:50] + "..." if len(content) > 50 else content
|
|
392
|
+
self.execution_tree.add(f"🧠 Reasoning: {reasoning_preview}", style="magenta")
|
|
393
|
+
|
|
394
|
+
self._update_live_display()
|
|
395
|
+
|
|
396
|
+
def _handle_cost_summary(self, event: CostSummaryEvent) -> None:
|
|
397
|
+
"""Handle cost summary display."""
|
|
398
|
+
cost = event.cost
|
|
399
|
+
total_tokens = event.tokens
|
|
400
|
+
cached_tokens = event.cached_tokens
|
|
401
|
+
# Cache fields available: event.cache_creation_input_tokens, event.cache_read_input_tokens
|
|
402
|
+
|
|
403
|
+
# Update footer with cost info
|
|
404
|
+
if cost or total_tokens or cached_tokens:
|
|
405
|
+
# Could update footer or show in details panel
|
|
406
|
+
# Example: Add cost/token/cache info to self.current_status or track in instance variable
|
|
407
|
+
pass
|
|
408
|
+
|
|
409
|
+
self._update_live_display()
|
|
410
|
+
|
|
411
|
+
# ============================================================================
|
|
412
|
+
# Interactive Prompt Examples
|
|
413
|
+
# ============================================================================
|
|
414
|
+
|
|
415
|
+
def prompt_for_confirmation(self, message: str) -> bool:
|
|
416
|
+
"""Example: Prompt user for confirmation.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
message: Confirmation message
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
True if confirmed, False otherwise
|
|
423
|
+
"""
|
|
424
|
+
if not self.interactive:
|
|
425
|
+
return True
|
|
426
|
+
|
|
427
|
+
# Pause live display for interaction
|
|
428
|
+
if self.live_display:
|
|
429
|
+
self.live_display.stop()
|
|
430
|
+
|
|
431
|
+
result = Confirm.ask(message)
|
|
432
|
+
|
|
433
|
+
# Resume live display
|
|
434
|
+
if self.live_display:
|
|
435
|
+
self.live_display.start()
|
|
436
|
+
|
|
437
|
+
return result
|
|
438
|
+
|
|
439
|
+
def prompt_for_input(self, message: str, default: str = "") -> str:
|
|
440
|
+
"""Example: Prompt user for text input.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
message: Prompt message
|
|
444
|
+
default: Default value
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
User input
|
|
448
|
+
"""
|
|
449
|
+
if not self.interactive:
|
|
450
|
+
return default
|
|
451
|
+
|
|
452
|
+
# Pause live display for interaction
|
|
453
|
+
if self.live_display:
|
|
454
|
+
self.live_display.stop()
|
|
455
|
+
|
|
456
|
+
result = Prompt.ask(message, default=default)
|
|
457
|
+
|
|
458
|
+
# Resume live display
|
|
459
|
+
if self.live_display:
|
|
460
|
+
self.live_display.start()
|
|
461
|
+
|
|
462
|
+
return result
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
# ============================================================================
|
|
466
|
+
# Example: Alternative implementation with Table instead of Tree
|
|
467
|
+
# ============================================================================
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
class LiveTableHandler(LiveTemplateHandler):
|
|
471
|
+
"""Alternative template using Table for step tracking.
|
|
472
|
+
|
|
473
|
+
Shows how to create variants of the same UI pattern.
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
def __init__(self, console: Console = None, interactive: bool = True):
|
|
477
|
+
super().__init__(console, interactive)
|
|
478
|
+
self.steps_table = Table(show_header=True, header_style="bold cyan")
|
|
479
|
+
self.steps_table.add_column("Step", style="cyan", width=8)
|
|
480
|
+
self.steps_table.add_column("Status", width=12)
|
|
481
|
+
self.steps_table.add_column("Action", overflow="fold")
|
|
482
|
+
|
|
483
|
+
def _render_tree(self) -> Panel:
|
|
484
|
+
"""Override to use Table instead of Tree."""
|
|
485
|
+
return Panel(
|
|
486
|
+
self.steps_table,
|
|
487
|
+
title="[bold]Execution Steps[/bold]",
|
|
488
|
+
border_style="green",
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
def _handle_step_start(self, event: StepStartEvent) -> None:
|
|
492
|
+
"""Add step as table row."""
|
|
493
|
+
step = event.step
|
|
494
|
+
self.steps_table.add_row(
|
|
495
|
+
f"Step {step}",
|
|
496
|
+
"[yellow]Running[/yellow]",
|
|
497
|
+
"Waiting for LLM...",
|
|
498
|
+
)
|
|
499
|
+
self._update_live_display()
|
|
500
|
+
|
|
501
|
+
# Override other handlers as needed to update table instead of tree...
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
# ============================================================================
|
|
505
|
+
# Helper function (add this to ui/helpers.py)
|
|
506
|
+
# ============================================================================
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def create_live_template_logger(interactive: bool = True):
|
|
510
|
+
"""Create logger using Live Template handler.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
interactive: Enable interactive prompts
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
CustomUILogger with LiveTemplateHandler
|
|
517
|
+
|
|
518
|
+
Example:
|
|
519
|
+
from tsugite.ui.helpers import create_live_template_logger
|
|
520
|
+
|
|
521
|
+
logger = create_live_template_logger(interactive=True)
|
|
522
|
+
# Use with agent runner
|
|
523
|
+
"""
|
|
524
|
+
from tsugite.ui.base import CustomUILogger
|
|
525
|
+
|
|
526
|
+
# from tsugite.ui.live_template import LiveTemplateHandler # After renaming
|
|
527
|
+
|
|
528
|
+
handler = LiveTemplateHandler(interactive=interactive)
|
|
529
|
+
return CustomUILogger(handler, handler.console)
|