feat: simplify exec render formatting
This commit is contained in:
@@ -8,6 +8,7 @@ from typing import Any
|
|||||||
|
|
||||||
STATUS_RUNNING = "▸"
|
STATUS_RUNNING = "▸"
|
||||||
STATUS_DONE = "✓"
|
STATUS_DONE = "✓"
|
||||||
|
STATUS_FAIL = "✗"
|
||||||
HEADER_SEP = " · "
|
HEADER_SEP = " · "
|
||||||
HARD_BREAK = " \n"
|
HARD_BREAK = " \n"
|
||||||
|
|
||||||
@@ -31,15 +32,17 @@ def format_elapsed(elapsed_s: float) -> str:
|
|||||||
def format_header(elapsed_s: float, turn: int | None, item: int | None, label: str) -> str:
|
def format_header(elapsed_s: float, turn: int | None, item: int | None, label: str) -> str:
|
||||||
elapsed = format_elapsed(elapsed_s)
|
elapsed = format_elapsed(elapsed_s)
|
||||||
parts = [label, elapsed]
|
parts = [label, elapsed]
|
||||||
if turn is not None:
|
|
||||||
parts.append(f"turn {turn}")
|
|
||||||
if item is not None:
|
if item is not None:
|
||||||
parts.append(f"item {item}")
|
parts.append(f"item {item}")
|
||||||
return HEADER_SEP.join(parts)
|
return HEADER_SEP.join(parts)
|
||||||
|
|
||||||
|
|
||||||
def is_command_log_line(line: str) -> bool:
|
def is_command_log_line(line: str) -> bool:
|
||||||
return f"{STATUS_RUNNING} running:" in line or f"{STATUS_DONE} ran:" in line
|
return (
|
||||||
|
f"{STATUS_RUNNING} " in line
|
||||||
|
or f"{STATUS_DONE} " in line
|
||||||
|
or f"{STATUS_FAIL} " in line
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def extract_numeric_id(item_id: object, fallback: int | None = None) -> int | None:
|
def extract_numeric_id(item_id: object, fallback: int | None = None) -> int | None:
|
||||||
@@ -89,7 +92,7 @@ def format_event(
|
|||||||
item = event["item"]
|
item = event["item"]
|
||||||
item_num = extract_numeric_id(item["id"], last_item)
|
item_num = extract_numeric_id(item["id"], last_item)
|
||||||
last_item = item_num if item_num is not None else last_item
|
last_item = item_num if item_num is not None else last_item
|
||||||
prefix = f"[{item_num if item_num is not None else '?'}] "
|
prefix = f"{item_num} "
|
||||||
|
|
||||||
match (item["type"], etype):
|
match (item["type"], etype):
|
||||||
case ("agent_message", "item.completed"):
|
case ("agent_message", "item.completed"):
|
||||||
@@ -106,7 +109,7 @@ def format_event(
|
|||||||
if command_width is not None:
|
if command_width is not None:
|
||||||
command = _shorten(command, command_width)
|
command = _shorten(command, command_width)
|
||||||
command = f"`{command}`"
|
command = f"`{command}`"
|
||||||
line = prefix + f"{STATUS_RUNNING} running: {command}"
|
line = prefix + f"{STATUS_RUNNING} {command}"
|
||||||
return last_item, [line], line, prefix
|
return last_item, [line], line, prefix
|
||||||
case ("command_execution", "item.completed"):
|
case ("command_execution", "item.completed"):
|
||||||
command = item["command"]
|
command = item["command"]
|
||||||
@@ -114,8 +117,13 @@ def format_event(
|
|||||||
command = _shorten(command, command_width)
|
command = _shorten(command, command_width)
|
||||||
command = f"`{command}`"
|
command = f"`{command}`"
|
||||||
exit_code = item["exit_code"]
|
exit_code = item["exit_code"]
|
||||||
exit_part = f" (exit {exit_code})" if exit_code is not None else ""
|
if exit_code == 0:
|
||||||
line = prefix + f"{STATUS_DONE} ran: {command}{exit_part}"
|
status = STATUS_DONE
|
||||||
|
exit_part = ""
|
||||||
|
else:
|
||||||
|
status = STATUS_FAIL if exit_code is not None else STATUS_DONE
|
||||||
|
exit_part = f" (exit {exit_code})" if exit_code is not None else ""
|
||||||
|
line = prefix + f"{status} {command}{exit_part}"
|
||||||
return last_item, [line], line, prefix
|
return last_item, [line], line, prefix
|
||||||
case ("mcp_tool_call", "item.started"):
|
case ("mcp_tool_call", "item.started"):
|
||||||
name = ".".join(part for part in (item["server"], item["tool"]) if part) or "tool"
|
name = ".".join(part for part in (item["server"], item["tool"]) if part) or "tool"
|
||||||
@@ -195,17 +203,12 @@ class ExecProgressRenderer:
|
|||||||
|
|
||||||
def render_progress(self, elapsed_s: float) -> str:
|
def render_progress(self, elapsed_s: float) -> str:
|
||||||
header = format_header(elapsed_s, self.turn_count, self.last_item, label="working")
|
header = format_header(elapsed_s, self.turn_count, self.last_item, label="working")
|
||||||
message = self._assemble(header, list(self.recent_actions))
|
return self._assemble(header, list(self.recent_actions))
|
||||||
return message if len(message) <= self.max_chars else header
|
|
||||||
|
|
||||||
def render_final(self, elapsed_s: float, answer: str, status: str = "done") -> str:
|
def render_final(self, elapsed_s: float, answer: str, status: str = "done") -> str:
|
||||||
header = format_header(elapsed_s, self.turn_count, self.last_item, label=status)
|
header = format_header(elapsed_s, self.turn_count, self.last_item, label=status)
|
||||||
lines = list(self.recent_actions)
|
|
||||||
if status == "done":
|
|
||||||
lines = [line for line in lines if not is_command_log_line(line)]
|
|
||||||
body = self._assemble(header, lines)
|
|
||||||
answer = (answer or "").strip()
|
answer = (answer or "").strip()
|
||||||
return body + ("\n\n" + answer if answer else "")
|
return header + ("\n\n" + answer if answer else "")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _assemble(header: str, lines: list[str]) -> str:
|
def _assemble(header: str, lines: list[str]) -> str:
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ def test_render_event_cli_sample_stream() -> None:
|
|||||||
assert out == [
|
assert out == [
|
||||||
"thread started",
|
"thread started",
|
||||||
"turn started",
|
"turn started",
|
||||||
"[0] **Searching for README files**",
|
"0 **Searching for README files**",
|
||||||
"[1] ▸ running: `bash -lc ls`",
|
"1 ▸ `bash -lc ls`",
|
||||||
"[1] ✓ ran: `bash -lc ls` (exit 0)",
|
"1 ✓ `bash -lc ls`",
|
||||||
"[2] **Checking repository root for README**",
|
"2 **Checking repository root for README**",
|
||||||
"assistant:",
|
"assistant:",
|
||||||
" Yep — there’s a `README.md` in the repository root.",
|
" Yep — there’s a `README.md` in the repository root.",
|
||||||
"turn completed",
|
"turn completed",
|
||||||
@@ -54,8 +54,8 @@ def test_render_event_cli_real_run_fixture() -> None:
|
|||||||
|
|
||||||
assert out[0] == "thread started"
|
assert out[0] == "thread started"
|
||||||
assert "turn started" in out
|
assert "turn started" in out
|
||||||
assert any(line.startswith("[0] ▸ running:") for line in out)
|
assert any(line.startswith("0 ▸ `") for line in out)
|
||||||
assert any(line.startswith("[0] ✓ ran:") for line in out)
|
assert any(line.startswith("0 ✓ `") for line in out)
|
||||||
assert "assistant:" in out
|
assert "assistant:" in out
|
||||||
assert any("exec-bridge" in line for line in out)
|
assert any("exec-bridge" in line for line in out)
|
||||||
assert out[-1] == "turn completed"
|
assert out[-1] == "turn completed"
|
||||||
@@ -67,11 +67,11 @@ def test_progress_renderer_renders_progress_and_final() -> None:
|
|||||||
r.note_event(evt)
|
r.note_event(evt)
|
||||||
|
|
||||||
progress = r.render_progress(3.0)
|
progress = r.render_progress(3.0)
|
||||||
assert progress.startswith("working · 3s · turn 1 · item 3")
|
assert progress.startswith("working · 3s · item 3")
|
||||||
assert "[1] ✓ ran: `bash -lc ls` (exit 0)" in progress
|
assert "1 ✓ `bash -lc ls`" in progress
|
||||||
|
|
||||||
final = r.render_final(3.0, "answer", status="done")
|
final = r.render_final(3.0, "answer", status="done")
|
||||||
assert final.startswith("done · 3s · turn 1 · item 3")
|
assert final.startswith("done · 3s · item 3")
|
||||||
assert "running:" not in final
|
assert "running:" not in final
|
||||||
assert "ran:" not in final
|
assert "ran:" not in final
|
||||||
assert final.endswith("answer")
|
assert final.endswith("answer")
|
||||||
|
|||||||
Reference in New Issue
Block a user