diff --git a/codex_retry_gateway_tui.py b/codex_retry_gateway_tui.py index a8dfd10..133a6de 100644 --- a/codex_retry_gateway_tui.py +++ b/codex_retry_gateway_tui.py @@ -22,7 +22,7 @@ from typing import Any 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_URL = "http://127.0.0.1:4610/__codex_retry_gateway" 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) -def primary_request_id(row: dict[str, Any]) -> str: - return str(row.get("response_id") or row.get("request_id") or "").strip() or "-" +def request_id_text(row: dict[str, Any]) -> str: + 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: @@ -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)) return ( 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_time(row["started_at"]), 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: bits = [ 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"started {short_time(row['started_at'])}", f"{row['method']} {row['path']}", @@ -1214,7 +1220,8 @@ def run_textual( self.request_table_width_profiles = { "compact": { "seq": 7, - "id": 18, + "req_id": 18, + "resp_id": 18, "thread": 18, "started": 19, "status": 7, @@ -1232,7 +1239,8 @@ def run_textual( }, "wide": { "seq": 7, - "id": 36, + "req_id": 24, + "resp_id": 24, "thread": 36, "started": 19, "status": 7, @@ -1272,7 +1280,8 @@ def run_textual( width_profile = self.request_table_width_profiles[self.request_table_density] self.request_table_column_keys = { "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"), "started": requests.add_column("Started", width=width_profile["started"], key="started"), "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"), "reasoning": requests.add_column("Reasoning", width=width_profile["reasoning"], key="reasoning"), "usage": requests.add_column("Usage", width=self.request_usage_width, key="usage"), - "req": requests.add_column("Req", width=width_profile["req"], key="req"), - "resp": requests.add_column("Resp", width=width_profile["resp"], key="resp"), + "req_bytes": requests.add_column("Req Size", width=width_profile["req"], key="req_bytes"), + "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"), "first": requests.add_column("First", width=width_profile["first"], key="first"), "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]: width_profile = self.request_table_width_profiles[self.request_table_density] limits = { - "id": width_profile["id"], + "req_id": width_profile["req_id"], + "resp_id": width_profile["resp_id"], "thread": width_profile["thread"], "path": width_profile["path"], "model": width_profile["model"], "usage": self.request_usage_width, "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) column = table.columns.get(key) if key is not None else None 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 []: try: 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(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)}" diff --git a/pyproject.toml b/pyproject.toml index 368e614..2d8420f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-retry-gateway-tui" -version = "0.1.3" +version = "0.1.4" description = "Terminal UI for codex-retry-gateway monitoring and control" readme = "README.md" requires-python = ">=3.11" diff --git a/tests/test_payload.py b/tests/test_payload.py index a5cde6f..bee8f34 100644 --- a/tests/test_payload.py +++ b/tests/test_payload.py @@ -94,8 +94,11 @@ class CodexRetryGatewayTUITests(unittest.TestCase): "finished_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("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.assertEqual(mod.request_updated_elapsed(row), "1.0s") 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.", "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}) - wide = mod.request_row_cells(row, {"id": 36, "thread": 36, "path": 40, "model": 24, "usage": 72, "note": 48}) - self.assertEqual(compact[9], "0B") - self.assertEqual(wide[9], "0B") + 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, {"req_id": 36, "resp_id": 36, "thread": 36, "path": 40, "model": 24, "usage": 72, "note": 48}) + self.assertEqual(compact[10], "0B") + self.assertEqual(wide[10], "0B") self.assertLess(len(compact[1]), len(wide[1])) self.assertLess(len(compact[2]), len(wide[2])) - self.assertLess(len(compact[5]), len(wide[5])) - self.assertLess(len(compact[8]), len(wide[8])) - self.assertLess(len(compact[15]), len(wide[15])) + self.assertLess(len(compact[3]), len(wide[3])) + self.assertLess(len(compact[6]), len(wide[6])) + 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: mod = load_module() @@ -174,8 +178,10 @@ class CodexRetryGatewayTUITests(unittest.TestCase): "finished_at": "2026-06-30T12:00:03Z", } cells = mod.request_row_cells(row) - self.assertEqual(cells[9], "1.5KB") - self.assertEqual(cells[10], "4.1KB") + self.assertEqual(cells[1], "req_1") + 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: mod = load_module() diff --git a/uv.lock b/uv.lock index 53c18a5..fd4742c 100644 --- a/uv.lock +++ b/uv.lock @@ -4,7 +4,7 @@ requires-python = ">=3.11" [[package]] name = "codex-retry-gateway-tui" -version = "0.1.0" +version = "0.1.4" source = { editable = "." } dependencies = [ { name = "textual" },