fix: derive streaming status from retry firsts
This commit is contained in:
@@ -23,7 +23,7 @@ from typing import Any
|
|||||||
|
|
||||||
|
|
||||||
APP_NAME = "codex-retry-gateway-tui"
|
APP_NAME = "codex-retry-gateway-tui"
|
||||||
FALLBACK_VERSION = "0.1.7"
|
FALLBACK_VERSION = "0.1.8"
|
||||||
DEFAULT_GATEWAY_ADMIN_PATH = "/__codex_retry_gateway"
|
DEFAULT_GATEWAY_ADMIN_PATH = "/__codex_retry_gateway"
|
||||||
DEFAULT_GATEWAY_URL = "http://127.0.0.1:4610/__codex_retry_gateway"
|
DEFAULT_GATEWAY_URL = "http://127.0.0.1:4610/__codex_retry_gateway"
|
||||||
DEFAULT_API_URL = DEFAULT_GATEWAY_URL
|
DEFAULT_API_URL = DEFAULT_GATEWAY_URL
|
||||||
@@ -870,6 +870,7 @@ def normalize_request_retry_firsts(value: Any) -> list[dict[str, Any]]:
|
|||||||
{
|
{
|
||||||
"round": parse_int_value(item.get("round")),
|
"round": parse_int_value(item.get("round")),
|
||||||
"slot": parse_int_value(item.get("slot")),
|
"slot": parse_int_value(item.get("slot")),
|
||||||
|
"first_response_at": item.get("first_response_at"),
|
||||||
"first_delay_ms": item.get("first_response_delay_ms", item.get("first_delay_ms", item.get("first_ms"))),
|
"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(),
|
"outcome": str(item.get("outcome") or "").strip(),
|
||||||
"status_code": item.get("status_code", item.get("upstream_status_code", item.get("status"))),
|
"status_code": item.get("status_code", item.get("upstream_status_code", item.get("status"))),
|
||||||
@@ -1339,13 +1340,38 @@ def request_match_score(row: dict[str, Any], needle: str) -> bool:
|
|||||||
return needle in haystack
|
return needle in haystack
|
||||||
|
|
||||||
|
|
||||||
|
def request_retry_first_received_at(row: dict[str, Any]) -> Any:
|
||||||
|
latest_value = None
|
||||||
|
latest_at = None
|
||||||
|
for first in row.get("reasoning_retry_current_firsts") or []:
|
||||||
|
if not isinstance(first, dict):
|
||||||
|
continue
|
||||||
|
value = first.get("first_response_at")
|
||||||
|
parsed = parse_datetime(value)
|
||||||
|
if parsed and (latest_at is None or parsed > latest_at):
|
||||||
|
latest_value = value
|
||||||
|
latest_at = parsed
|
||||||
|
return latest_value
|
||||||
|
|
||||||
|
|
||||||
|
def request_has_received_first(row: dict[str, Any]) -> bool:
|
||||||
|
if parse_datetime(row.get("first_response_at")):
|
||||||
|
return True
|
||||||
|
for first in row.get("reasoning_retry_current_firsts") or []:
|
||||||
|
if not isinstance(first, dict):
|
||||||
|
continue
|
||||||
|
if parse_datetime(first.get("first_response_at")) or parse_int_value(first.get("first_delay_ms")) is not None:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def request_lifecycle_label(row: dict[str, Any]) -> str:
|
def request_lifecycle_label(row: dict[str, Any]) -> str:
|
||||||
state = str(row.get("lifecycle_state") or "").strip().lower()
|
state = str(row.get("lifecycle_state") or "").strip().lower()
|
||||||
if request_is_discarded(row):
|
if request_is_discarded(row):
|
||||||
return "discarded"
|
return "discarded"
|
||||||
if state in {"finish", "finished", "complete", "completed"} or parse_datetime(row.get("finished_at")):
|
if state in {"finish", "finished", "complete", "completed"} or parse_datetime(row.get("finished_at")):
|
||||||
return "finished"
|
return "finished"
|
||||||
if state in {"receive_first", "streaming"} or parse_datetime(row.get("first_response_at")):
|
if state in {"receive_first", "streaming"} or request_has_received_first(row):
|
||||||
return "streaming"
|
return "streaming"
|
||||||
return "waiting"
|
return "waiting"
|
||||||
|
|
||||||
@@ -1368,11 +1394,12 @@ def request_is_discarded(row: dict[str, Any]) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def request_activity_at(row: dict[str, Any]) -> Any:
|
def request_activity_at(row: dict[str, Any]) -> Any:
|
||||||
for key in ("last_activity_at", "first_response_at", "started_at"):
|
candidates = [row.get("last_activity_at"), row.get("first_response_at"), request_retry_first_received_at(row), row.get("started_at")]
|
||||||
value = row.get(key)
|
parsed_candidates = [(parse_datetime(value), value) for value in candidates]
|
||||||
if parse_datetime(value):
|
valid_candidates = [(parsed, value) for parsed, value in parsed_candidates if parsed]
|
||||||
return value
|
if not valid_candidates:
|
||||||
return None
|
return None
|
||||||
|
return max(valid_candidates, key=lambda item: item[0])[1]
|
||||||
|
|
||||||
|
|
||||||
def request_activity_age_seconds(row: dict[str, Any], now: dt.datetime | None = None) -> float | None:
|
def request_activity_age_seconds(row: dict[str, Any], now: dt.datetime | None = None) -> float | None:
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "codex-retry-gateway-tui"
|
name = "codex-retry-gateway-tui"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
description = "Terminal UI for codex-retry-gateway monitoring and control"
|
description = "Terminal UI for codex-retry-gateway monitoring and control"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -143,6 +143,18 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
|
|||||||
"first_response_at": "2026-07-10T11:59:55Z",
|
"first_response_at": "2026-07-10T11:59:55Z",
|
||||||
"last_activity_at": "2026-07-10T11:59:55Z",
|
"last_activity_at": "2026-07-10T11:59:55Z",
|
||||||
}
|
}
|
||||||
|
retry_streaming = {
|
||||||
|
"lifecycle_state": "sent",
|
||||||
|
"started_at": "2026-07-10T11:59:00Z",
|
||||||
|
"reasoning_retry_current_firsts": [
|
||||||
|
{
|
||||||
|
"round": 1,
|
||||||
|
"slot": 1,
|
||||||
|
"first_response_at": "2026-07-10T11:59:58Z",
|
||||||
|
"first_delay_ms": 2200,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
complete = {"lifecycle_state": "finish", "status_code": 200}
|
complete = {"lifecycle_state": "finish", "status_code": 200}
|
||||||
discarded = {
|
discarded = {
|
||||||
"lifecycle_state": "finish",
|
"lifecycle_state": "finish",
|
||||||
@@ -161,6 +173,8 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(mod.request_status_label(waiting, now), "waiting")
|
self.assertEqual(mod.request_status_label(waiting, now), "waiting")
|
||||||
self.assertEqual(mod.request_status_label(streaming, now), "streaming")
|
self.assertEqual(mod.request_status_label(streaming, now), "streaming")
|
||||||
|
self.assertEqual(mod.request_status_label(retry_streaming, now), "streaming")
|
||||||
|
self.assertEqual(mod.request_activity_at(retry_streaming), "2026-07-10T11:59:58Z")
|
||||||
self.assertEqual(mod.request_status_label(complete, now), "HTTP 200")
|
self.assertEqual(mod.request_status_label(complete, now), "HTTP 200")
|
||||||
self.assertEqual(mod.request_status_label(discarded, now), "discarded")
|
self.assertEqual(mod.request_status_label(discarded, now), "discarded")
|
||||||
self.assertEqual(mod.request_status_label(slow_waiting, now), "waiting 30.0s")
|
self.assertEqual(mod.request_status_label(slow_waiting, now), "waiting 30.0s")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ requires-python = ">=3.11"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codex-retry-gateway-tui"
|
name = "codex-retry-gateway-tui"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "textual" },
|
{ name = "textual" },
|
||||||
|
|||||||
Reference in New Issue
Block a user