diff --git a/README.md b/README.md index 5c72a9d..119e9f4 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,16 @@ uv tool install git+https://gitea.shujk.top/shujakuin/codex-retry-gateway-tui.gi codex-retry-gateway-tui ``` +Install as a user command with Pixi global from the published Gitea repo: + +```bash +pixi global install \ + --git https://gitea.shujk.top/shujakuin/codex-retry-gateway-tui.git \ + --expose codex-retry-gateway-tui \ + --expose codex-gateway-tui \ + codex-retry-gateway-tui +``` + Default behavior: ```text @@ -61,7 +71,7 @@ uv run codex-retry-gateway-tui Views: - overview: current gateway status and summary counts -- requests: recent requests, response/request/thread IDs, timing, usage, cached ratio, attempts, and `usage_last_updated_at` +- requests: recent requests, response/request/thread IDs, timing, usage, cached ratio, attempts, reasoning effort/summary, and `usage_last_updated_at` - logs: recent gateway logs - profiles: saved profiles, active profile default selection, profile actions diff --git a/codex_retry_gateway_tui.py b/codex_retry_gateway_tui.py index 133a6de..d3e9a0c 100644 --- a/codex_retry_gateway_tui.py +++ b/codex_retry_gateway_tui.py @@ -472,10 +472,27 @@ def request_chunk_progress(row: dict[str, Any]) -> str: return f"{row.get('stream_chunk_count') or 0} / {format_bytes(row.get('response_bytes_received'))}" +def request_reasoning_mode_summary(row: dict[str, Any]) -> str: + effort = str(row.get("reasoning_effort") or "").strip() + summary = str(row.get("reasoning_summary") or "").strip() + bits = [] + if effort: + bits.append(effort) + if summary: + bits.append(f"summary {summary}") + return " | ".join(bits) + + def request_row_cells(row: dict[str, Any], text_limits: dict[str, int] | None = None) -> tuple[Any, ...]: limits = text_limits or {} note_prefix = f"{row['upstream_attempt_count'] or 0}x " note_width = max(4, limits.get("note", 22) - len(note_prefix)) + note_parts = [] + reasoning_mode = request_reasoning_mode_summary(row) + if reasoning_mode: + note_parts.append(reasoning_mode) + if row.get("error"): + note_parts.append(str(row["error"])) return ( str(row["seq"]), short_text(request_id_text(row), limits.get("req_id", 18)) or "-", @@ -493,7 +510,7 @@ def request_row_cells(row: dict[str, Any], text_limits: dict[str, int] | None = format_duration_ms_as_seconds(row["first_response_delay_ms"]), format_duration_ms_as_seconds(row["duration_ms"]), request_updated_elapsed(row), - f"{note_prefix}{short_text(row['error'], note_width)}", + f"{note_prefix}{short_text(' | '.join(note_parts), note_width)}", ) @@ -688,6 +705,8 @@ def request_match_score(row: dict[str, Any], needle: str) -> bool: "model", "requested_model", "forwarded_model", + "reasoning_effort", + "reasoning_summary", "error", "upstream_origin", "upstream_path", @@ -745,6 +764,8 @@ def normalize_request_rows(payload: dict[str, Any], filter_text: str = "") -> li "model": str(entry.get("model") or ""), "requested_model": str(entry.get("requested_model") or ""), "forwarded_model": str(entry.get("forwarded_model") or ""), + "reasoning_effort": str(entry.get("reasoning_effort") or ""), + "reasoning_summary": str(entry.get("reasoning_summary") or ""), "request_stream": bool(entry.get("request_stream")), "response_stream": bool(entry.get("response_stream")), "matched": bool(entry.get("matched")), @@ -805,6 +826,10 @@ def render_request_detail(row: dict[str, Any]) -> str: bits.append(f"requested {row['requested_model']}") if row.get("forwarded_model") and row.get("forwarded_model") != row.get("model"): bits.append(f"forwarded {row['forwarded_model']}") + if row.get("reasoning_effort"): + bits.append(f"effort {row['reasoning_effort']}") + if row.get("reasoning_summary"): + bits.append(f"summary {row['reasoning_summary']}") if row.get("reasoning_tokens") is not None: bits.append(f"reasoning {row['reasoning_tokens']}") if row.get("input_tokens") is not None or row.get("output_tokens") is not None or row.get("total_tokens") is not None or row.get("cached_tokens") is not None: diff --git a/tests/test_payload.py b/tests/test_payload.py index bee8f34..eb91c85 100644 --- a/tests/test_payload.py +++ b/tests/test_payload.py @@ -35,14 +35,16 @@ class CodexRetryGatewayTUITests(unittest.TestCase): mod = load_module() payload = { "entries": [ - {"seq": 1, "request_id": "req_a", "response_id": "resp_a", "thread_id": "thread_a", "path": "/responses"}, - {"seq": 2, "request_id": "req_b", "response_id": "resp_b", "thread_id": "thread_b", "path": "/v1/responses"}, + {"seq": 1, "request_id": "req_a", "response_id": "resp_a", "thread_id": "thread_a", "path": "/responses", "reasoning_effort": "high"}, + {"seq": 2, "request_id": "req_b", "response_id": "resp_b", "thread_id": "thread_b", "path": "/v1/responses", "reasoning_effort": "xhigh", "reasoning_summary": "auto"}, ] } rows = mod.normalize_request_rows(payload) self.assertEqual([row["request_id"] for row in rows], ["req_b", "req_a"]) self.assertEqual(rows[0]["response_id"], "resp_b") self.assertEqual(rows[0]["thread_id"], "thread_b") + self.assertEqual(rows[0]["reasoning_effort"], "xhigh") + self.assertEqual(rows[0]["reasoning_summary"], "auto") def test_profile_rows_show_active_first(self) -> None: mod = load_module() @@ -84,6 +86,8 @@ class CodexRetryGatewayTUITests(unittest.TestCase): "status_code": 200, "upstream_status_code": 200, "upstream_attempt_count": 1, + "reasoning_effort": "xhigh", + "reasoning_summary": "auto", "first_response_delay_ms": 10, "duration_ms": 20, "request_body_bytes": 3, @@ -100,6 +104,8 @@ class CodexRetryGatewayTUITests(unittest.TestCase): 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("effort xhigh", mod.render_request_detail(row)) + self.assertIn("summary auto", mod.render_request_detail(row)) self.assertEqual(mod.request_updated_elapsed(row), "1.0s") self.assertEqual(mod.request_chunk_progress(row), "7 / 4.1KB") @@ -164,6 +170,7 @@ class CodexRetryGatewayTUITests(unittest.TestCase): "requested_model": "", "forwarded_model": "", "reasoning_tokens": 7, + "reasoning_effort": "xhigh", "input_tokens": 20, "output_tokens": 10, "cached_tokens": 5, @@ -182,6 +189,7 @@ class CodexRetryGatewayTUITests(unittest.TestCase): self.assertEqual(cells[2], "resp_1") self.assertEqual(cells[10], "1.5KB") self.assertEqual(cells[11], "4.1KB") + self.assertIn("xhigh", cells[16]) def test_request_rows_read_usage_from_nested_usage_object(self) -> None: mod = load_module()