From 184b6b65b0a7d2d40b981a4520ee5b36efdd09f9 Mon Sep 17 00:00:00 2001 From: yunyaozhou Date: Tue, 7 Jul 2026 20:28:13 +0800 Subject: [PATCH] feat: improve retry first column controls --- codex_retry_gateway_tui.py | 177 +++++++++++++++++++++++++------------ tests/test_payload.py | 16 ++-- 2 files changed, 132 insertions(+), 61 deletions(-) diff --git a/codex_retry_gateway_tui.py b/codex_retry_gateway_tui.py index a1d8905..f27a6c2 100644 --- a/codex_retry_gateway_tui.py +++ b/codex_retry_gateway_tui.py @@ -81,6 +81,51 @@ REQUEST_TABLE_COLUMN_KEY_SET = set(REQUEST_TABLE_COLUMN_KEYS) REQUEST_TABLE_CELL_INDEX = {key: index for index, key in enumerate(REQUEST_TABLE_COLUMN_KEYS)} DEFAULT_REQUEST_TABLE_VISIBLE_COLUMNS = REQUEST_TABLE_COLUMN_KEYS DEFAULT_REQUEST_TABLE_SORT_COLUMN = "seq" +REQUEST_TABLE_WIDTH_PROFILES = { + "compact": { + "seq": 7, + "req_id": 18, + "resp_id": 18, + "thread": 18, + "started": 19, + "status": 7, + "path": 22, + "model": 16, + "effort": 8, + "reasoning": 8, + "token_col": 8, + "req": 10, + "resp": 10, + "chunks": 14, + "first": 12, + "duration": 8, + "updated": 8, + "note": 22, + "round": 10, + }, + "wide": { + "seq": 7, + "req_id": 24, + "resp_id": 24, + "thread": 36, + "started": 19, + "status": 7, + "path": 40, + "model": 24, + "effort": 8, + "reasoning": 8, + "token_col": 8, + "req": 12, + "resp": 12, + "chunks": 16, + "first": 14, + "duration": 8, + "updated": 8, + "note": 48, + "round": 10, + }, +} +REQUEST_TABLE_MIN_COLUMN_WIDTH = 4 REQUEST_TABLE_COLUMN_ALIASES = { "seq": ("seq",), "req_id": ("req id", "request id", "request_id"), @@ -295,6 +340,29 @@ def request_table_hidden_columns(value: Any, columns: list[str]) -> list[str]: return hidden +def request_table_column_width_value(value: Any) -> int | None: + try: + width = int(str(value or "").strip()) + except Exception: + return None + if width <= 0: + return None + return max(REQUEST_TABLE_MIN_COLUMN_WIDTH, width) + + +def request_table_width_overrides(value: Any) -> dict[str, int]: + requested = value if isinstance(value, dict) else {} + overrides: dict[str, int] = {} + for raw_key, raw_width in requested.items(): + width = request_table_column_width_value(raw_width) + if width is None: + continue + for column_key in request_table_column_keys(raw_key): + if column_key: + overrides[column_key] = width + return overrides + + def normalize_request_table_sort_column(value: Any) -> str: column_key = str(value or "").strip() if column_key in REQUEST_TABLE_COLUMN_KEY_SET: @@ -320,9 +388,11 @@ def normalize_request_table_preferences(data: Any) -> dict[str, Any]: hidden_columns = request_table_hidden_columns(source.get("hidden_columns"), columns) if len(hidden_columns) >= len(columns) and columns: hidden_columns = [column_key for column_key in hidden_columns if column_key != columns[0]] + width_overrides = request_table_width_overrides(source.get("widths")) return { "columns": columns, "hidden_columns": hidden_columns, + "widths": {column_key: width for column_key, width in width_overrides.items() if column_key in columns}, } @@ -338,6 +408,7 @@ def request_table_visible_columns(preferences: dict[str, Any]) -> list[str]: def request_table_preferences_from_text(text: str) -> dict[str, Any]: columns = [] hidden_columns = [] + width_overrides: dict[str, int] = {} seen: set[str] = set() for line_number, raw_line in enumerate(text.splitlines(), start=1): stripped = raw_line.strip() @@ -350,6 +421,11 @@ def request_table_preferences_from_text(text: str) -> dict[str, Any]: token = token[1:].strip() if not token: continue + width = None + width_match = re.match(r"^(.*?)(?:\s+(\d+))?$", token) + if width_match: + token = (width_match.group(1) or "").strip() + width = request_table_column_width_value(width_match.group(2)) column_keys = request_table_column_keys(token) if not column_keys: if hidden: @@ -362,6 +438,8 @@ def request_table_preferences_from_text(text: str) -> dict[str, Any]: columns.append(column_key) if hidden: hidden_columns.append(column_key) + if width is not None: + width_overrides[column_key] = width if not columns: raise ValueError("no request columns found in editor document") missing = [column_key for column_key in REQUEST_TABLE_COLUMN_KEYS if column_key not in seen] @@ -369,6 +447,7 @@ def request_table_preferences_from_text(text: str) -> dict[str, Any]: { "columns": columns + missing, "hidden_columns": hidden_columns + missing, + "widths": width_overrides, } ) @@ -376,17 +455,20 @@ def request_table_preferences_from_text(text: str) -> dict[str, Any]: def request_table_preferences_document(preferences: dict[str, Any]) -> str: normalized = normalize_request_table_preferences(preferences) hidden = set(normalized["hidden_columns"]) + widths = normalized["widths"] lines = [ "# Request table columns.", "# One column key per line.", "# Prefix with # to hide a column.", + "# Optional: append an integer width, e.g. `first 14` or `in 10`.", "# Move lines up or down to change display order.", "# Common keys: in, out, cache, req_id, resp_id, req_bytes, resp_bytes, round.", "", ] for column_key in normalized["columns"]: prefix = "# " if column_key in hidden else "" - lines.append(f"{prefix}{column_key}") + width_suffix = f" {widths[column_key]}" if column_key in widths else "" + lines.append(f"{prefix}{column_key}{width_suffix}") return "\n".join(lines).rstrip() + "\n" @@ -769,6 +851,7 @@ def normalize_request_retry_firsts(value: Any) -> list[dict[str, Any]]: continue firsts.append( { + "round": parse_int_value(item.get("round")), "slot": parse_int_value(item.get("slot")), "first_delay_ms": item.get("first_response_delay_ms", item.get("first_delay_ms", item.get("first_ms"))), "outcome": str(item.get("outcome") or "").strip(), @@ -781,6 +864,20 @@ def normalize_request_retry_firsts(value: Any) -> list[dict[str, Any]]: return firsts +def request_retry_first_label(first: dict[str, Any], fallback_round: Any = None) -> str: + round_number = parse_int_value(first.get("round")) + if round_number is None: + round_number = parse_int_value(fallback_round) + slot = parse_int_value(first.get("slot")) + if round_number is not None: + if slot is not None and slot > 1: + return f"{round_number}-{slot}" + return str(round_number) + if slot is not None: + return str(slot) + return "?" + + def request_retry_round_text(row: dict[str, Any]) -> str: round_number = parse_int_value(row.get("reasoning_retry_current_round")) width = parse_int_value(row.get("reasoning_retry_current_width")) @@ -793,8 +890,11 @@ def request_retry_round_text(row: dict[str, Any]) -> str: return f"{round_number}({max(0, width)})" -def request_retry_first_summary(first: dict[str, Any]) -> str: +def request_retry_first_summary(first: dict[str, Any], fallback_round: Any = None) -> str: bits = [] + round_label = request_retry_first_label(first, fallback_round) + if round_label != "?": + bits.append(f"round {round_label}") slot = parse_int_value(first.get("slot")) if slot is not None: bits.append(f"slot {slot}") @@ -823,10 +923,11 @@ def request_retry_first_summary(first: dict[str, Any]) -> str: def request_retry_wave_summary(row: dict[str, Any]) -> str: summaries = [] + fallback_round = row.get("reasoning_retry_current_round") for first in row.get("reasoning_retry_current_firsts") or []: if not isinstance(first, dict): continue - summary = request_retry_first_summary(first) + summary = request_retry_first_summary(first, fallback_round) if summary: summaries.append(summary) return "; ".join(summaries) @@ -838,10 +939,7 @@ def request_retry_firsts_compact_text(row: dict[str, Any]) -> str: if not isinstance(first, dict): continue delay = format_duration_ms_as_seconds(first.get("first_delay_ms")) - if delay == "-": - delay = str(first.get("outcome") or "-").strip() or "-" - slot = parse_int_value(first.get("slot")) - parts.append(f"{slot if slot is not None else '?'}:{delay}") + parts.append(delay if delay != "-" else "?") return " ".join(parts) @@ -1744,48 +1842,7 @@ def run_textual( self.request_table_preferences = load_request_table_preferences() self.request_table_density = "compact" self.request_table_width_profiles = { - "compact": { - "seq": 7, - "req_id": 18, - "resp_id": 18, - "thread": 18, - "started": 19, - "status": 7, - "path": 22, - "model": 16, - "effort": 8, - "reasoning": 8, - "token_col": 8, - "req": 10, - "resp": 10, - "chunks": 14, - "first": 8, - "duration": 8, - "updated": 8, - "note": 22, - "round": 10, - }, - "wide": { - "seq": 7, - "req_id": 24, - "resp_id": 24, - "thread": 36, - "started": 19, - "status": 7, - "path": 40, - "model": 24, - "effort": 8, - "reasoning": 8, - "token_col": 8, - "req": 12, - "resp": 12, - "chunks": 16, - "first": 8, - "duration": 8, - "updated": 8, - "note": 48, - "round": 10, - }, + density: dict(widths) for density, widths in REQUEST_TABLE_WIDTH_PROFILES.items() } def compose(self) -> ComposeResult: @@ -1875,6 +1932,16 @@ def run_textual( return False return True + def _request_table_column_width(self, column_key: str, density: str | None = None) -> int: + normalized = normalize_request_table_preferences(self.request_table_preferences) + override = request_table_column_width_value(normalized.get("widths", {}).get(column_key)) + if override is not None: + return override + active_density = density or self.request_table_density + width_profile = self.request_table_width_profiles[active_density] + column = REQUEST_TABLE_COLUMN_BY_KEY[column_key] + return width_profile[column["width_key"]] + def _request_row_key(self, row: dict[str, Any]) -> str: return f"{row['seq']}:{row['request_id']}" @@ -1897,12 +1964,11 @@ def run_textual( table = self.query_one("#requests_table", DataTable) selected_row_index = table.cursor_row if table.cursor_row is not None and table.cursor_row >= 0 else 0 selected_row_key = self._selected_request_key() - width_profile = self.request_table_width_profiles[self.request_table_density] table.clear(columns=True) self.request_table_column_keys = {} for column_key in self._visible_request_column_keys(): column = REQUEST_TABLE_COLUMN_BY_KEY[column_key] - width = width_profile[column["width_key"]] + width = self._request_table_column_width(column_key) self.request_table_column_keys[column_key] = table.add_column( column["label"], width=width, @@ -2140,10 +2206,11 @@ def run_textual( "path": width_profile["path"], "model": width_profile["model"], "effort": width_profile["effort"], + "first": width_profile["first"], "note": width_profile["note"], "round": width_profile["round"], } - for name in ("req_id", "resp_id", "thread", "path", "model", "effort", "note", "round"): + for name in ("req_id", "resp_id", "thread", "path", "model", "effort", "first", "note", "round"): 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): @@ -2164,13 +2231,11 @@ def run_textual( def _apply_request_table_width_profile(self, density: str) -> None: table = self.query_one("#requests_table", DataTable) - width_profile = self.request_table_width_profiles[density] for name, key in self.request_table_column_keys.items(): column = table.columns.get(key) - width_key = REQUEST_TABLE_COLUMN_BY_KEY.get(name, {}).get("width_key", name) - if column is None or width_key not in width_profile: + if column is None: continue - column.width = width_profile[width_key] + column.width = self._request_table_column_width(name, density) self.request_table_density = density self._rerender_requests_after_layout_change() self._set_status(f"request table mode {density}") diff --git a/tests/test_payload.py b/tests/test_payload.py index 1ce1dae..2c32543 100644 --- a/tests/test_payload.py +++ b/tests/test_payload.py @@ -230,6 +230,7 @@ class CodexRetryGatewayTUITests(unittest.TestCase): "reasoning_retry_current_width": 2, "reasoning_retry_current_firsts": [ { + "round": 3, "slot": 1, "first_response_delay_ms": 1200, "outcome": "ok", @@ -238,6 +239,7 @@ class CodexRetryGatewayTUITests(unittest.TestCase): "reasoning_tokens": 516, }, { + "round": 3, "slot": 2, "first_delay_ms": 1800, "outcome": "retry", @@ -254,10 +256,11 @@ class CodexRetryGatewayTUITests(unittest.TestCase): self.assertEqual(row["reasoning_retry_current_width"], 2) self.assertEqual(len(row["reasoning_retry_current_firsts"]), 2) self.assertEqual(mod.request_retry_round_text(row), "3(2)") - self.assertIn("1:1.2s", mod.request_first_text(row)) - self.assertIn("slot 1", mod.request_retry_wave_summary(row)) + self.assertEqual(mod.request_first_text(row), "1.2s 1.8s") + self.assertIn("round 3 slot 1", mod.request_retry_wave_summary(row)) self.assertIn("retry round 3(2)", mod.render_request_detail(row)) - self.assertIn("current wave slot 1 first 1.2s", mod.render_request_detail(row)) + self.assertIn("first 1.2s 1.8s", mod.render_request_detail(row)) + self.assertIn("current wave round 3 slot 1 first 1.2s", mod.render_request_detail(row)) self.assertEqual(mod.request_row_cells(row)[20], "3(2)") def test_request_table_preferences_round_trip(self) -> None: @@ -273,12 +276,14 @@ class CodexRetryGatewayTUITests(unittest.TestCase): { "columns": ["round", "seq", "status", "unknown"], "hidden_columns": ["status", "unknown"], + "widths": {"round": 14, "status": 9, "unknown": 99}, } ) self.assertEqual(saved_path, prefs_path) loaded = mod.load_request_table_preferences() self.assertEqual(loaded["columns"][:3], ["round", "seq", "status"]) self.assertEqual(loaded["hidden_columns"], ["status"]) + self.assertEqual(loaded["widths"], {"round": 14, "status": 9}) def test_request_table_preferences_from_text_hides_and_reorders_columns(self) -> None: mod = load_module() @@ -286,9 +291,9 @@ class CodexRetryGatewayTUITests(unittest.TestCase): "\n".join( [ "# comment", - "thread", + "thread 24", "seq", - "# cache", + "# cache 9", "in", "out", "", @@ -297,6 +302,7 @@ class CodexRetryGatewayTUITests(unittest.TestCase): ) self.assertEqual(prefs["columns"][:6], ["thread", "seq", "cache", "in", "out", "req_id"]) self.assertIn("cache", prefs["hidden_columns"]) + self.assertEqual(prefs["widths"], {"thread": 24, "cache": 9}) def test_request_table_preferences_expand_legacy_usage_column(self) -> None: mod = load_module()