Show reasoning mode in request views

This commit is contained in:
2026-07-04 02:57:59 +08:00
parent 5c7bea431a
commit 6fdedd997b
3 changed files with 47 additions and 4 deletions
+11 -1
View File
@@ -17,6 +17,16 @@ uv tool install git+https://gitea.shujk.top/shujakuin/codex-retry-gateway-tui.gi
codex-retry-gateway-tui 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: Default behavior:
```text ```text
@@ -61,7 +71,7 @@ uv run codex-retry-gateway-tui
Views: Views:
- overview: current gateway status and summary counts - 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 - logs: recent gateway logs
- profiles: saved profiles, active profile default selection, profile actions - profiles: saved profiles, active profile default selection, profile actions
+26 -1
View File
@@ -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'))}" 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, ...]: def request_row_cells(row: dict[str, Any], text_limits: dict[str, int] | None = None) -> tuple[Any, ...]:
limits = text_limits or {} limits = text_limits or {}
note_prefix = f"{row['upstream_attempt_count'] or 0}x " note_prefix = f"{row['upstream_attempt_count'] or 0}x "
note_width = max(4, limits.get("note", 22) - len(note_prefix)) 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 ( return (
str(row["seq"]), str(row["seq"]),
short_text(request_id_text(row), limits.get("req_id", 18)) or "-", 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["first_response_delay_ms"]),
format_duration_ms_as_seconds(row["duration_ms"]), format_duration_ms_as_seconds(row["duration_ms"]),
request_updated_elapsed(row), 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", "model",
"requested_model", "requested_model",
"forwarded_model", "forwarded_model",
"reasoning_effort",
"reasoning_summary",
"error", "error",
"upstream_origin", "upstream_origin",
"upstream_path", "upstream_path",
@@ -745,6 +764,8 @@ def normalize_request_rows(payload: dict[str, Any], filter_text: str = "") -> li
"model": str(entry.get("model") or ""), "model": str(entry.get("model") or ""),
"requested_model": str(entry.get("requested_model") or ""), "requested_model": str(entry.get("requested_model") or ""),
"forwarded_model": str(entry.get("forwarded_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")), "request_stream": bool(entry.get("request_stream")),
"response_stream": bool(entry.get("response_stream")), "response_stream": bool(entry.get("response_stream")),
"matched": bool(entry.get("matched")), "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']}") bits.append(f"requested {row['requested_model']}")
if row.get("forwarded_model") and row.get("forwarded_model") != row.get("model"): if row.get("forwarded_model") and row.get("forwarded_model") != row.get("model"):
bits.append(f"forwarded {row['forwarded_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: if row.get("reasoning_tokens") is not None:
bits.append(f"reasoning {row['reasoning_tokens']}") 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: 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:
+10 -2
View File
@@ -35,14 +35,16 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
mod = load_module() mod = load_module()
payload = { payload = {
"entries": [ "entries": [
{"seq": 1, "request_id": "req_a", "response_id": "resp_a", "thread_id": "thread_a", "path": "/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"}, {"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) rows = mod.normalize_request_rows(payload)
self.assertEqual([row["request_id"] for row in rows], ["req_b", "req_a"]) 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]["response_id"], "resp_b")
self.assertEqual(rows[0]["thread_id"], "thread_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: def test_profile_rows_show_active_first(self) -> None:
mod = load_module() mod = load_module()
@@ -84,6 +86,8 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
"status_code": 200, "status_code": 200,
"upstream_status_code": 200, "upstream_status_code": 200,
"upstream_attempt_count": 1, "upstream_attempt_count": 1,
"reasoning_effort": "xhigh",
"reasoning_summary": "auto",
"first_response_delay_ms": 10, "first_response_delay_ms": 10,
"duration_ms": 20, "duration_ms": 20,
"request_body_bytes": 3, "request_body_bytes": 3,
@@ -100,6 +104,8 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
self.assertIn("req r", 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("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.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_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")
@@ -164,6 +170,7 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
"requested_model": "", "requested_model": "",
"forwarded_model": "", "forwarded_model": "",
"reasoning_tokens": 7, "reasoning_tokens": 7,
"reasoning_effort": "xhigh",
"input_tokens": 20, "input_tokens": 20,
"output_tokens": 10, "output_tokens": 10,
"cached_tokens": 5, "cached_tokens": 5,
@@ -182,6 +189,7 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
self.assertEqual(cells[2], "resp_1") self.assertEqual(cells[2], "resp_1")
self.assertEqual(cells[10], "1.5KB") self.assertEqual(cells[10], "1.5KB")
self.assertEqual(cells[11], "4.1KB") self.assertEqual(cells[11], "4.1KB")
self.assertIn("xhigh", cells[16])
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()