refactor: make exec render happy path

This commit is contained in:
banteg
2025-12-29 02:46:07 +04:00
parent ae6874e1df
commit 1a926cf300
@@ -1,6 +1,5 @@
from __future__ import annotations from __future__ import annotations
import json
import re import re
from collections import deque from collections import deque
from dataclasses import dataclass, field from dataclasses import dataclass, field
@@ -21,7 +20,7 @@ MAX_PROGRESS_CHARS = 300
def _one_line(text: str) -> str: def _one_line(text: str) -> str:
return " ".join((text or "").split()) return " ".join(text.split())
def _truncate(text: str, max_len: int) -> str: def _truncate(text: str, max_len: int) -> str:
@@ -114,8 +113,6 @@ def _with_id(item_id: Optional[int], line: str) -> str:
def _truncate_output(text: str, max_lines: int = 20, max_chars: int = 4000) -> str: def _truncate_output(text: str, max_lines: int = 20, max_chars: int = 4000) -> str:
if not text:
return ""
if len(text) > max_chars: if len(text) > max_chars:
text = text[-max_chars:] text = text[-max_chars:]
lines = text.splitlines() lines = text.splitlines()
@@ -124,13 +121,6 @@ def _truncate_output(text: str, max_lines: int = 20, max_chars: int = 4000) -> s
return "\n".join(lines) return "\n".join(lines)
def _maybe_parse_json(text: str) -> Optional[Any]:
try:
return json.loads(text)
except json.JSONDecodeError:
return None
@dataclass @dataclass
class ExecRenderState: class ExecRenderState:
recent_actions: deque[str] = field(default_factory=lambda: deque(maxlen=5)) recent_actions: deque[str] = field(default_factory=lambda: deque(maxlen=5))
@@ -142,11 +132,9 @@ class ExecRenderState:
def _record_item(state: ExecRenderState, item: dict[str, Any]) -> None: def _record_item(state: ExecRenderState, item: dict[str, Any]) -> None:
item_id = item.get("id") numeric_id = _extract_numeric_id(item["id"])
if isinstance(item_id, (int, str)): if numeric_id is not None:
numeric_id = _extract_numeric_id(item_id) state.last_turn = numeric_id
if numeric_id is not None:
state.last_turn = numeric_id
def _set_current_action(state: ExecRenderState, item_id: Optional[int], line: str) -> bool: def _set_current_action(state: ExecRenderState, item_id: Optional[int], line: str) -> bool:
@@ -186,7 +174,7 @@ def render_event_cli(
*, *,
show_output: bool = False, show_output: bool = False,
) -> list[str]: ) -> list[str]:
etype = event.get("type") etype = event["type"]
lines: list[str] = [] lines: list[str] = []
if etype == "thread.started": if etype == "thread.started":
@@ -199,59 +187,53 @@ def render_event_cli(
return ["turn completed"] return ["turn completed"]
if etype == "turn.failed": if etype == "turn.failed":
error = event.get("error", {}).get("message", "") error = event["error"]["message"]
return [f"turn failed: {error}"] return [f"turn failed: {error}"]
if etype == "error": if etype == "error":
return [f"stream error: {event.get('message', '')}"] return [f"stream error: {event['message']}"]
if etype in {"item.started", "item.updated", "item.completed"}: if etype in {"item.started", "item.updated", "item.completed"}:
item = event.get("item", {}) or {} item = event["item"]
_record_item(state, item) _record_item(state, item)
itype = item.get("type") itype = item["type"]
item_num = _extract_numeric_id(item.get("id"), state.last_turn) item_num = _extract_numeric_id(item["id"], state.last_turn)
if itype == "agent_message" and etype == "item.completed": if itype == "agent_message" and etype == "item.completed":
text = item.get("text", "") lines.append("assistant:")
parsed = _maybe_parse_json(text) lines.extend(indent(item["text"], " ").splitlines())
if parsed is not None:
lines.append("assistant (json):")
lines.extend(indent(json.dumps(parsed, indent=2), " ").splitlines())
else:
lines.append("assistant:")
lines.extend(indent(text, " ").splitlines() if text else [" (empty)"])
elif itype == "command_execution": elif itype == "command_execution":
command = _format_command(item.get("command", "")) command = _format_command(item["command"])
if etype == "item.started": if etype == "item.started":
lines.append(_with_id(item_num, f"{STATUS_RUNNING} running: {command}")) lines.append(_with_id(item_num, f"{STATUS_RUNNING} running: {command}"))
elif etype == "item.completed": elif etype == "item.completed":
exit_code = item.get("exit_code") exit_code = item["exit_code"]
exit_part = f" (exit {exit_code})" if exit_code is not None else "" exit_part = f" (exit {exit_code})" if exit_code is not None else ""
lines.append(_with_id(item_num, f"{STATUS_DONE} ran: {command}{exit_part}")) lines.append(_with_id(item_num, f"{STATUS_DONE} ran: {command}{exit_part}"))
if show_output: if show_output:
output = _truncate_output(item.get("aggregated_output", "")) output = _truncate_output(item["aggregated_output"])
if output: if output:
lines.extend(indent(output, " ").splitlines()) lines.extend(indent(output, " ").splitlines())
elif itype == "file_change" and etype == "item.completed": elif itype == "file_change" and etype == "item.completed":
line = _format_file_change(item.get("changes", [])) line = _format_file_change(item["changes"])
lines.append(_with_id(item_num, f"{STATUS_DONE} {line}")) lines.append(_with_id(item_num, f"{STATUS_DONE} {line}"))
elif itype == "mcp_tool_call": elif itype == "mcp_tool_call":
name = _format_tool_call(item.get("server", ""), item.get("tool", "")) name = _format_tool_call(item["server"], item["tool"])
if etype == "item.started": if etype == "item.started":
lines.append(_with_id(item_num, f"{STATUS_RUNNING} tool: {name}")) lines.append(_with_id(item_num, f"{STATUS_RUNNING} tool: {name}"))
elif etype == "item.completed": elif etype == "item.completed":
lines.append(_with_id(item_num, f"{STATUS_DONE} tool: {name}")) lines.append(_with_id(item_num, f"{STATUS_DONE} tool: {name}"))
elif itype == "web_search" and etype == "item.completed": elif itype == "web_search" and etype == "item.completed":
query = _format_query(item.get("query", "")) query = _format_query(item["query"])
lines.append(_with_id(item_num, f"{STATUS_DONE} searched: {query}")) lines.append(_with_id(item_num, f"{STATUS_DONE} searched: {query}"))
elif itype == "error" and etype == "item.completed": elif itype == "error" and etype == "item.completed":
warning = _truncate(item.get("message", ""), 120) warning = _truncate(item["message"], 120)
lines.append(_with_id(item_num, f"{STATUS_DONE} warning: {warning}")) lines.append(_with_id(item_num, f"{STATUS_DONE} warning: {warning}"))
return lines return lines
@@ -264,20 +246,20 @@ class ExecProgressRenderer:
self.max_chars = max_chars self.max_chars = max_chars
def note_event(self, event: dict[str, Any]) -> bool: def note_event(self, event: dict[str, Any]) -> bool:
etype = event.get("type") etype = event["type"]
changed = False changed = False
if etype in {"thread.started", "turn.started"}: if etype in {"thread.started", "turn.started"}:
return True return True
if etype in {"item.started", "item.updated", "item.completed"}: if etype in {"item.started", "item.updated", "item.completed"}:
item = event.get("item", {}) or {} item = event["item"]
_record_item(self.state, item) _record_item(self.state, item)
itype = item.get("type") itype = item["type"]
item_id = _extract_numeric_id(item.get("id"), self.state.last_turn) item_id = _extract_numeric_id(item["id"], self.state.last_turn)
if itype == "reasoning": if itype == "reasoning":
reasoning = _format_reasoning(item.get("text", "")) reasoning = _format_reasoning(item["text"])
if reasoning: if reasoning:
reasoning_line = _with_id(item_id, reasoning) reasoning_line = _with_id(item_id, reasoning)
if self.state.current_action and not self.state.current_reasoning: if self.state.current_action and not self.state.current_reasoning:
@@ -288,19 +270,19 @@ class ExecProgressRenderer:
return changed return changed
if itype == "command_execution": if itype == "command_execution":
command = _format_command(item.get("command", "")) command = _format_command(item["command"])
if etype == "item.started": if etype == "item.started":
line = _with_id(item_id, f"{STATUS_RUNNING} running: {command}") line = _with_id(item_id, f"{STATUS_RUNNING} running: {command}")
changed = _set_current_action(self.state, item_id, line) or changed changed = _set_current_action(self.state, item_id, line) or changed
elif etype == "item.completed": elif etype == "item.completed":
exit_code = item.get("exit_code") exit_code = item["exit_code"]
exit_part = f" (exit {exit_code})" if exit_code is not None else "" exit_part = f" (exit {exit_code})" if exit_code is not None else ""
line = _with_id(item_id, f"{STATUS_DONE} ran: {command}{exit_part}") line = _with_id(item_id, f"{STATUS_DONE} ran: {command}{exit_part}")
changed = _complete_action(self.state, item_id, line) or changed changed = _complete_action(self.state, item_id, line) or changed
return changed return changed
if itype == "mcp_tool_call": if itype == "mcp_tool_call":
name = _format_tool_call(item.get("server", ""), item.get("tool", "")) name = _format_tool_call(item["server"], item["tool"])
if etype == "item.started": if etype == "item.started":
line = _with_id(item_id, f"{STATUS_RUNNING} tool: {name}") line = _with_id(item_id, f"{STATUS_RUNNING} tool: {name}")
changed = _set_current_action(self.state, item_id, line) or changed changed = _set_current_action(self.state, item_id, line) or changed
@@ -310,16 +292,16 @@ class ExecProgressRenderer:
return changed return changed
if itype == "web_search" and etype == "item.completed": if itype == "web_search" and etype == "item.completed":
query = _format_query(item.get("query", "")) query = _format_query(item["query"])
line = _with_id(item_id, f"{STATUS_DONE} searched: {query}") line = _with_id(item_id, f"{STATUS_DONE} searched: {query}")
return _complete_action(self.state, item_id, line) or changed return _complete_action(self.state, item_id, line) or changed
if itype == "file_change" and etype == "item.completed": if itype == "file_change" and etype == "item.completed":
line = _with_id(item_id, f"{STATUS_DONE} {_format_file_change(item.get('changes', []))}") line = _with_id(item_id, f"{STATUS_DONE} {_format_file_change(item['changes'])}")
return _complete_action(self.state, item_id, line) or changed return _complete_action(self.state, item_id, line) or changed
if itype == "error" and etype == "item.completed": if itype == "error" and etype == "item.completed":
warning = _truncate(item.get("message", ""), 120) warning = _truncate(item["message"], 120)
line = _with_id(item_id, f"{STATUS_DONE} warning: {warning}") line = _with_id(item_id, f"{STATUS_DONE} warning: {warning}")
return _complete_action(self.state, item_id, line) or changed return _complete_action(self.state, item_id, line) or changed