feat: clarify request lifecycle status
This commit is contained in:
+104
-27
@@ -23,7 +23,7 @@ from typing import Any
|
||||
|
||||
|
||||
APP_NAME = "codex-retry-gateway-tui"
|
||||
FALLBACK_VERSION = "0.1.6"
|
||||
FALLBACK_VERSION = "0.1.7"
|
||||
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
|
||||
@@ -47,6 +47,8 @@ DEFAULT_PROFILE_RETRYABLE_ERROR_MESSAGES = [
|
||||
DEFAULT_PROFILE_UPSTREAM_FETCH_RETRY_ATTEMPTS = 5
|
||||
DEFAULT_PROFILE_UPSTREAM_FETCH_RETRY_BACKOFF_MS = 350
|
||||
DEFAULT_PROFILE_REQUEST_HISTORY_LIMIT = 0
|
||||
REQUEST_STATUS_SLOW_AFTER_SECONDS = 20
|
||||
REQUEST_STATUS_STALLED_AFTER_SECONDS = 120
|
||||
DEFAULT_PROFILE_ENDPOINTS = [
|
||||
"/responses",
|
||||
"/chat/completions",
|
||||
@@ -102,7 +104,7 @@ REQUEST_TABLE_WIDTH_PROFILES = {
|
||||
"resp_id": 18,
|
||||
"thread": 18,
|
||||
"started": 19,
|
||||
"status": 7,
|
||||
"status": 12,
|
||||
"path": 22,
|
||||
"model": 16,
|
||||
"effort": 8,
|
||||
@@ -123,7 +125,7 @@ REQUEST_TABLE_WIDTH_PROFILES = {
|
||||
"resp_id": 24,
|
||||
"thread": 36,
|
||||
"started": 19,
|
||||
"status": 7,
|
||||
"status": 14,
|
||||
"path": 40,
|
||||
"model": 24,
|
||||
"effort": 8,
|
||||
@@ -986,7 +988,7 @@ def request_row_cells(row: dict[str, Any], text_limits: dict[str, int] | None =
|
||||
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 '-'}",
|
||||
request_status_label(row),
|
||||
short_text(row["path"], limits.get("path", 22)),
|
||||
short_text(request_model_text(row), limits.get("model", 16)),
|
||||
short_text(request_effort_text(row), limits.get("effort", 10)),
|
||||
@@ -1020,16 +1022,16 @@ def request_visible_cells(
|
||||
visible_column_keys: list[str],
|
||||
) -> list[Any]:
|
||||
visible_cells = [cells[REQUEST_TABLE_CELL_INDEX[column_key]] for column_key in visible_column_keys]
|
||||
thread_color = request_thread_color(row.get("thread_id"))
|
||||
if not thread_color:
|
||||
return visible_cells
|
||||
try:
|
||||
from rich.text import Text
|
||||
except ImportError:
|
||||
return visible_cells
|
||||
thread_color = request_thread_color(row.get("thread_id"))
|
||||
styled_cells: list[Any] = []
|
||||
for column_key, cell in zip(visible_column_keys, visible_cells):
|
||||
if column_key in THREAD_COLOR_COLUMN_KEYS:
|
||||
if column_key == "status":
|
||||
styled_cells.append(Text(str(cell), style=request_status_style(row)))
|
||||
elif thread_color and column_key in THREAD_COLOR_COLUMN_KEYS:
|
||||
styled_cells.append(Text(str(cell), style=f"bold {thread_color}"))
|
||||
else:
|
||||
styled_cells.append(cell)
|
||||
@@ -1316,6 +1318,8 @@ def request_match_score(row: dict[str, Any], needle: str) -> bool:
|
||||
"requested_model",
|
||||
"forwarded_model",
|
||||
"reasoning_effort",
|
||||
"lifecycle_state",
|
||||
"discard_reason",
|
||||
"error",
|
||||
"upstream_origin",
|
||||
"upstream_path",
|
||||
@@ -1335,25 +1339,92 @@ def request_match_score(row: dict[str, Any], needle: str) -> bool:
|
||||
return needle in haystack
|
||||
|
||||
|
||||
def status_symbol(entry: dict[str, Any]) -> str:
|
||||
if entry.get("error"):
|
||||
return "!"
|
||||
if entry.get("matched"):
|
||||
return "*"
|
||||
if entry.get("response_stream"):
|
||||
return "~"
|
||||
return " "
|
||||
|
||||
|
||||
def request_lifecycle_label(row: dict[str, Any]) -> str:
|
||||
state = str(row.get("lifecycle_state") or "").strip().lower()
|
||||
if state == "finish":
|
||||
return "done"
|
||||
if state == "receive_first":
|
||||
return "first"
|
||||
if state == "streaming":
|
||||
return "live"
|
||||
return "sent"
|
||||
if request_is_discarded(row):
|
||||
return "discarded"
|
||||
if state in {"finish", "finished", "complete", "completed"} or parse_datetime(row.get("finished_at")):
|
||||
return "finished"
|
||||
if state in {"receive_first", "streaming"} or parse_datetime(row.get("first_response_at")):
|
||||
return "streaming"
|
||||
return "waiting"
|
||||
|
||||
|
||||
def request_is_discarded(row: dict[str, Any]) -> bool:
|
||||
state = str(row.get("lifecycle_state") or "").strip().lower()
|
||||
if bool(row.get("discarded")) or state in {"discarded", "abandoned", "cancelled", "canceled"}:
|
||||
return True
|
||||
error = str(row.get("error") or "").strip().lower()
|
||||
return any(
|
||||
marker in error
|
||||
for marker in (
|
||||
"client disconnected",
|
||||
"client closed",
|
||||
"cancelled by client",
|
||||
"canceled by client",
|
||||
"request aborted",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def request_activity_at(row: dict[str, Any]) -> Any:
|
||||
for key in ("last_activity_at", "first_response_at", "started_at"):
|
||||
value = row.get(key)
|
||||
if parse_datetime(value):
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def request_activity_age_seconds(row: dict[str, Any], now: dt.datetime | None = None) -> float | None:
|
||||
activity_at = parse_datetime(request_activity_at(row))
|
||||
if not activity_at:
|
||||
return None
|
||||
current = now or dt.datetime.now(activity_at.tzinfo)
|
||||
if current.tzinfo is None and activity_at.tzinfo is not None:
|
||||
current = current.replace(tzinfo=activity_at.tzinfo)
|
||||
return max(0.0, (current - activity_at).total_seconds())
|
||||
|
||||
|
||||
def request_status_label(row: dict[str, Any], now: dt.datetime | None = None) -> str:
|
||||
if request_is_discarded(row):
|
||||
return "discarded"
|
||||
status_code = parse_int_value(row.get("status_code"))
|
||||
if status_code is not None:
|
||||
return f"HTTP {status_code}"
|
||||
lifecycle = request_lifecycle_label(row)
|
||||
if lifecycle == "finished":
|
||||
return "finished"
|
||||
age_seconds = request_activity_age_seconds(row, now)
|
||||
if age_seconds is not None and age_seconds >= REQUEST_STATUS_STALLED_AFTER_SECONDS:
|
||||
return f"stalled {format_elapsed_seconds(age_seconds)}"
|
||||
if age_seconds is not None and age_seconds >= REQUEST_STATUS_SLOW_AFTER_SECONDS:
|
||||
return f"{lifecycle} {format_elapsed_seconds(age_seconds)}"
|
||||
return lifecycle
|
||||
|
||||
|
||||
def request_status_style(row: dict[str, Any], now: dt.datetime | None = None) -> str:
|
||||
label = request_status_label(row, now).lower()
|
||||
if label.startswith(("discarded", "stalled")):
|
||||
return "bold #ff5f5f"
|
||||
if label.startswith("waiting"):
|
||||
return "bold #ffaf5f"
|
||||
if label.startswith("streaming"):
|
||||
return "bold #5fd7ff"
|
||||
if label.startswith("http "):
|
||||
status_code = parse_int_value(row.get("status_code")) or 0
|
||||
if 200 <= status_code < 400:
|
||||
return "bold #87d75f"
|
||||
if 400 <= status_code < 500:
|
||||
return "bold #ffaf5f"
|
||||
return "bold #ff5f5f"
|
||||
if row.get("error"):
|
||||
return "bold #ff5f5f"
|
||||
return "#b0b0b0"
|
||||
|
||||
|
||||
def status_symbol(entry: dict[str, Any]) -> str:
|
||||
"""Compatibility helper for callers that previously consumed a status marker."""
|
||||
return request_status_label(entry)
|
||||
|
||||
|
||||
def normalize_request_rows(payload: dict[str, Any], filter_text: str = "") -> list[dict[str, Any]]:
|
||||
@@ -1381,6 +1452,9 @@ def normalize_request_rows(payload: dict[str, Any], filter_text: str = "") -> li
|
||||
"reasoning_summary": str(entry.get("reasoning_summary") or ""),
|
||||
"request_stream": bool(entry.get("request_stream")),
|
||||
"response_stream": bool(entry.get("response_stream")),
|
||||
"lifecycle_state": str(entry.get("lifecycle_state") or ""),
|
||||
"discarded": bool(entry.get("discarded")),
|
||||
"discard_reason": str(entry.get("discard_reason") or ""),
|
||||
"matched": bool(entry.get("matched")),
|
||||
"inspected": bool(entry.get("inspected")),
|
||||
"status_code": entry.get("status_code"),
|
||||
@@ -1394,6 +1468,7 @@ def normalize_request_rows(payload: dict[str, Any], filter_text: str = "") -> li
|
||||
"request_body_bytes": entry.get("request_body_bytes"),
|
||||
"response_bytes_received": entry.get("response_bytes_received"),
|
||||
"stream_chunk_count": entry.get("stream_chunk_count"),
|
||||
"first_response_at": entry.get("first_response_at"),
|
||||
"first_response_delay_ms": entry.get("first_response_delay_ms"),
|
||||
"reasoning_retry_current_round": parse_int_value(entry.get("reasoning_retry_current_round")),
|
||||
"reasoning_retry_current_width": parse_int_value(entry.get("reasoning_retry_current_width")),
|
||||
@@ -1425,7 +1500,7 @@ def render_request_detail(row: dict[str, Any]) -> str:
|
||||
f"started {short_time(row['started_at'])}",
|
||||
f"{row['method']} {row['path']}",
|
||||
f"profile {row.get('profile_name') or '-'}",
|
||||
f"status {row['status_code'] or '-'}",
|
||||
f"status {request_status_label(row)}",
|
||||
f"upstream {row['upstream_status_code'] or '-'}",
|
||||
f"attempts {row['upstream_attempt_count'] or 0}",
|
||||
f"first {request_first_text(row)}",
|
||||
@@ -1456,6 +1531,8 @@ def render_request_detail(row: dict[str, Any]) -> str:
|
||||
bits.append(f"cached raw {format_count(row['cached_tokens'])}")
|
||||
if row.get("error"):
|
||||
bits.append(f"error {row['error']}")
|
||||
if row.get("discard_reason"):
|
||||
bits.append(f"discard {row['discard_reason']}")
|
||||
if row.get("upstream_origin"):
|
||||
bits.append(f"origin {row['upstream_origin']}")
|
||||
if row.get("upstream_path"):
|
||||
@@ -2742,7 +2819,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(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"{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} {short_text(request_status_label(row), 14):<14} "
|
||||
f"{short_text(row['path'], 20):<20} {short_text(row['model'] or row['requested_model'] or row['forwarded_model'], 16):<16} {short_text(request_effort_text(row), 6):<6} {request_reasoning_tokens_text(row):<6} "
|
||||
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_retry_note(row), 30)}"
|
||||
|
||||
Reference in New Issue
Block a user