feat: show req and resp ids in tui

This commit is contained in:
2026-07-01 10:03:20 +08:00
parent 58f3caeea5
commit 5c7bea431a
4 changed files with 41 additions and 25 deletions
+23 -13
View File
@@ -22,7 +22,7 @@ from typing import Any
APP_NAME = "codex-retry-gateway-tui" APP_NAME = "codex-retry-gateway-tui"
FALLBACK_VERSION = "0.1.0" FALLBACK_VERSION = "0.1.4"
DEFAULT_GATEWAY_ADMIN_PATH = "/__codex_retry_gateway" DEFAULT_GATEWAY_ADMIN_PATH = "/__codex_retry_gateway"
DEFAULT_GATEWAY_URL = "http://127.0.0.1:4610/__codex_retry_gateway" DEFAULT_GATEWAY_URL = "http://127.0.0.1:4610/__codex_retry_gateway"
DEFAULT_API_URL = DEFAULT_GATEWAY_URL DEFAULT_API_URL = DEFAULT_GATEWAY_URL
@@ -386,8 +386,12 @@ def effective_input_tokens(input_tokens: Any, cached_tokens: Any) -> int | None:
return max(0, total - cached) return max(0, total - cached)
def primary_request_id(row: dict[str, Any]) -> str: def request_id_text(row: dict[str, Any]) -> str:
return str(row.get("response_id") or row.get("request_id") or "").strip() or "-" return str(row.get("request_id") or "").strip() or "-"
def response_id_text(row: dict[str, Any]) -> str:
return str(row.get("response_id") or "").strip() or "-"
def request_usage_summary(row: dict[str, Any]) -> str: def request_usage_summary(row: dict[str, Any]) -> str:
@@ -474,7 +478,8 @@ def request_row_cells(row: dict[str, Any], text_limits: dict[str, int] | None =
note_width = max(4, limits.get("note", 22) - len(note_prefix)) note_width = max(4, limits.get("note", 22) - len(note_prefix))
return ( return (
str(row["seq"]), str(row["seq"]),
short_text(primary_request_id(row), limits.get("id", 18)) or "-", short_text(request_id_text(row), limits.get("req_id", 18)) or "-",
short_text(response_id_text(row), limits.get("resp_id", 18)) or "-",
short_text(row["thread_id"], limits.get("thread", 18)) or "-", short_text(row["thread_id"], limits.get("thread", 18)) or "-",
short_time(row["started_at"]), short_time(row["started_at"]),
f"{status_symbol(row)} {row.get('status_code') or '-'}", f"{status_symbol(row)} {row.get('status_code') or '-'}",
@@ -776,7 +781,8 @@ def normalize_request_rows(payload: dict[str, Any], filter_text: str = "") -> li
def render_request_detail(row: dict[str, Any]) -> str: def render_request_detail(row: dict[str, Any]) -> str:
bits = [ bits = [
f"seq {row['seq']}", f"seq {row['seq']}",
f"id {primary_request_id(row)}", f"req {request_id_text(row)}",
f"resp {response_id_text(row)}",
f"thread {row.get('thread_id') or '-'}", f"thread {row.get('thread_id') or '-'}",
f"started {short_time(row['started_at'])}", f"started {short_time(row['started_at'])}",
f"{row['method']} {row['path']}", f"{row['method']} {row['path']}",
@@ -1214,7 +1220,8 @@ def run_textual(
self.request_table_width_profiles = { self.request_table_width_profiles = {
"compact": { "compact": {
"seq": 7, "seq": 7,
"id": 18, "req_id": 18,
"resp_id": 18,
"thread": 18, "thread": 18,
"started": 19, "started": 19,
"status": 7, "status": 7,
@@ -1232,7 +1239,8 @@ def run_textual(
}, },
"wide": { "wide": {
"seq": 7, "seq": 7,
"id": 36, "req_id": 24,
"resp_id": 24,
"thread": 36, "thread": 36,
"started": 19, "started": 19,
"status": 7, "status": 7,
@@ -1272,7 +1280,8 @@ def run_textual(
width_profile = self.request_table_width_profiles[self.request_table_density] width_profile = self.request_table_width_profiles[self.request_table_density]
self.request_table_column_keys = { self.request_table_column_keys = {
"seq": requests.add_column("Seq", width=width_profile["seq"], key="seq"), "seq": requests.add_column("Seq", width=width_profile["seq"], key="seq"),
"id": requests.add_column("ID", width=width_profile["id"], key="id"), "req_id": requests.add_column("Req ID", width=width_profile["req_id"], key="req_id"),
"resp_id": requests.add_column("Resp ID", width=width_profile["resp_id"], key="resp_id"),
"thread": requests.add_column("Thread", width=width_profile["thread"], key="thread"), "thread": requests.add_column("Thread", width=width_profile["thread"], key="thread"),
"started": requests.add_column("Started", width=width_profile["started"], key="started"), "started": requests.add_column("Started", width=width_profile["started"], key="started"),
"status": requests.add_column("Status", width=width_profile["status"], key="status"), "status": requests.add_column("Status", width=width_profile["status"], key="status"),
@@ -1280,8 +1289,8 @@ def run_textual(
"model": requests.add_column("Model", width=width_profile["model"], key="model"), "model": requests.add_column("Model", width=width_profile["model"], key="model"),
"reasoning": requests.add_column("Reasoning", width=width_profile["reasoning"], key="reasoning"), "reasoning": requests.add_column("Reasoning", width=width_profile["reasoning"], key="reasoning"),
"usage": requests.add_column("Usage", width=self.request_usage_width, key="usage"), "usage": requests.add_column("Usage", width=self.request_usage_width, key="usage"),
"req": requests.add_column("Req", width=width_profile["req"], key="req"), "req_bytes": requests.add_column("Req Size", width=width_profile["req"], key="req_bytes"),
"resp": requests.add_column("Resp", width=width_profile["resp"], key="resp"), "resp_bytes": requests.add_column("Resp Size", width=width_profile["resp"], key="resp_bytes"),
"chunks": requests.add_column("Chunks", width=width_profile["chunks"], key="chunks"), "chunks": requests.add_column("Chunks", width=width_profile["chunks"], key="chunks"),
"first": requests.add_column("First", width=width_profile["first"], key="first"), "first": requests.add_column("First", width=width_profile["first"], key="first"),
"duration": requests.add_column("Duration", width=width_profile["duration"], key="duration"), "duration": requests.add_column("Duration", width=width_profile["duration"], key="duration"),
@@ -1559,14 +1568,15 @@ def run_textual(
def _request_text_limits(self, table: DataTable) -> dict[str, int]: def _request_text_limits(self, table: DataTable) -> dict[str, int]:
width_profile = self.request_table_width_profiles[self.request_table_density] width_profile = self.request_table_width_profiles[self.request_table_density]
limits = { limits = {
"id": width_profile["id"], "req_id": width_profile["req_id"],
"resp_id": width_profile["resp_id"],
"thread": width_profile["thread"], "thread": width_profile["thread"],
"path": width_profile["path"], "path": width_profile["path"],
"model": width_profile["model"], "model": width_profile["model"],
"usage": self.request_usage_width, "usage": self.request_usage_width,
"note": width_profile["note"], "note": width_profile["note"],
} }
for name in ("id", "thread", "path", "model", "usage", "note"): for name in ("req_id", "resp_id", "thread", "path", "model", "usage", "note"):
key = self.request_table_column_keys.get(name) key = self.request_table_column_keys.get(name)
column = table.columns.get(key) if key is not None else None column = table.columns.get(key) if key is not None else None
if column is None or not getattr(column, "width", 0): if column is None or not getattr(column, "width", 0):
@@ -1791,7 +1801,7 @@ def print_once(snapshot: dict[str, Any], filter_text: str = "") -> None:
for row in snapshot.get("requests") or []: for row in snapshot.get("requests") or []:
try: try:
print( print(
f"{row['seq']:>6} {short_text(primary_request_id(row), 16):<16} {short_text(row.get('thread_id'), 14):<14} {short_time(row.get('started_at')):<19} {status_symbol(row)} {row.get('status_code') or '-':<4} " f"{row['seq']:>6} {short_text(request_id_text(row), 16):<16} {short_text(response_id_text(row), 16):<16} {short_text(row.get('thread_id'), 14):<14} {short_time(row.get('started_at')):<19} {status_symbol(row)} {row.get('status_code') or '-':<4} "
f"{short_text(row['path'], 20):<20} {short_text(row['model'] or row['requested_model'] or row['forwarded_model'], 16):<16} " f"{short_text(row['path'], 20):<20} {short_text(row['model'] or row['requested_model'] or row['forwarded_model'], 16):<16} "
f"{short_text(request_usage_summary(row), 26):<26} {format_bytes(row.get('request_body_bytes')):<8} {request_chunk_progress(row):<14} {format_duration_ms_as_seconds(row['duration_ms']):<8} {request_updated_elapsed(row):<8} " f"{short_text(request_usage_summary(row), 26):<26} {format_bytes(row.get('request_body_bytes')):<8} {request_chunk_progress(row):<14} {format_duration_ms_as_seconds(row['duration_ms']):<8} {request_updated_elapsed(row):<8} "
f"{format_count(row['upstream_attempt_count'] or 0):<4} {short_text(row['error'], 30)}" f"{format_count(row['upstream_attempt_count'] or 0):<4} {short_text(row['error'], 30)}"
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "codex-retry-gateway-tui" name = "codex-retry-gateway-tui"
version = "0.1.3" version = "0.1.4"
description = "Terminal UI for codex-retry-gateway monitoring and control" description = "Terminal UI for codex-retry-gateway monitoring and control"
readme = "README.md" readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.11"
+16 -10
View File
@@ -94,8 +94,11 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
"finished_at": "2026-06-30T00:00:01Z", "finished_at": "2026-06-30T00:00:01Z",
"usage_last_updated_at": "2026-06-30T00:00:01Z", "usage_last_updated_at": "2026-06-30T00:00:01Z",
} }
self.assertEqual(mod.primary_request_id(row), "resp_1") self.assertEqual(mod.request_id_text(row), "r")
self.assertEqual(mod.response_id_text(row), "resp_1")
self.assertIn("thread thread_1", mod.render_request_detail(row)) self.assertIn("thread thread_1", mod.render_request_detail(row))
self.assertIn("req r", mod.render_request_detail(row))
self.assertIn("resp resp_1", mod.render_request_detail(row))
self.assertIn("updated 1.0s", mod.render_request_detail(row)) self.assertIn("updated 1.0s", mod.render_request_detail(row))
self.assertEqual(mod.request_updated_elapsed(row), "1.0s") self.assertEqual(mod.request_updated_elapsed(row), "1.0s")
self.assertEqual(mod.request_chunk_progress(row), "7 / 4.1KB") self.assertEqual(mod.request_chunk_progress(row), "7 / 4.1KB")
@@ -136,15 +139,16 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
"error": "Selected model is at capacity. Please try a different model.", "error": "Selected model is at capacity. Please try a different model.",
"finished_at": "2026-06-30T12:25:00Z", "finished_at": "2026-06-30T12:25:00Z",
} }
compact = mod.request_row_cells(row, {"id": 18, "thread": 18, "path": 22, "model": 16, "usage": 24, "note": 22}) compact = mod.request_row_cells(row, {"req_id": 18, "resp_id": 18, "thread": 18, "path": 22, "model": 16, "usage": 24, "note": 22})
wide = mod.request_row_cells(row, {"id": 36, "thread": 36, "path": 40, "model": 24, "usage": 72, "note": 48}) wide = mod.request_row_cells(row, {"req_id": 36, "resp_id": 36, "thread": 36, "path": 40, "model": 24, "usage": 72, "note": 48})
self.assertEqual(compact[9], "0B") self.assertEqual(compact[10], "0B")
self.assertEqual(wide[9], "0B") self.assertEqual(wide[10], "0B")
self.assertLess(len(compact[1]), len(wide[1])) self.assertLess(len(compact[1]), len(wide[1]))
self.assertLess(len(compact[2]), len(wide[2])) self.assertLess(len(compact[2]), len(wide[2]))
self.assertLess(len(compact[5]), len(wide[5])) self.assertLess(len(compact[3]), len(wide[3]))
self.assertLess(len(compact[8]), len(wide[8])) self.assertLess(len(compact[6]), len(wide[6]))
self.assertLess(len(compact[15]), len(wide[15])) self.assertLess(len(compact[9]), len(wide[9]))
self.assertLess(len(compact[16]), len(wide[16]))
def test_request_row_cells_show_request_bytes(self) -> None: def test_request_row_cells_show_request_bytes(self) -> None:
mod = load_module() mod = load_module()
@@ -174,8 +178,10 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
"finished_at": "2026-06-30T12:00:03Z", "finished_at": "2026-06-30T12:00:03Z",
} }
cells = mod.request_row_cells(row) cells = mod.request_row_cells(row)
self.assertEqual(cells[9], "1.5KB") self.assertEqual(cells[1], "req_1")
self.assertEqual(cells[10], "4.1KB") self.assertEqual(cells[2], "resp_1")
self.assertEqual(cells[10], "1.5KB")
self.assertEqual(cells[11], "4.1KB")
def test_request_rows_read_usage_from_nested_usage_object(self) -> None: def test_request_rows_read_usage_from_nested_usage_object(self) -> None:
mod = load_module() mod = load_module()
Generated
+1 -1
View File
@@ -4,7 +4,7 @@ requires-python = ">=3.11"
[[package]] [[package]]
name = "codex-retry-gateway-tui" name = "codex-retry-gateway-tui"
version = "0.1.0" version = "0.1.4"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "textual" }, { name = "textual" },