diff --git a/bin/omarchy-agent-usage-codex b/bin/omarchy-agent-usage-codex index 972c164d..6e2d918f 100755 --- a/bin/omarchy-agent-usage-codex +++ b/bin/omarchy-agent-usage-codex @@ -544,12 +544,20 @@ def fetch_codex_rpc(): rpc_request(proc, 1, "initialize", {"clientInfo": {"name": "omarchy-agent-usage", "version": "1"}}, timeout=8) proc.stdin.write(json.dumps({"method": "initialized", "params": {}}) + "\n") proc.stdin.flush() - account_msg = rpc_request(proc, 2, "account/read", timeout=4) - limits_msg = rpc_request(proc, 3, "account/rateLimits/read", timeout=4) - - account = (account_msg.get("result") or {}).get("account") or {} + limits_msg = rpc_request(proc, 2, "account/rateLimits/read", timeout=8) limits = (limits_msg.get("result") or {}).get("rateLimits") or {} - plan = limits.get("planType") or account.get("planType") or account.get("type") or "" + plan = limits.get("planType") or "" + + # The limits name the plan themselves. account/read is only a fallback + # for when they don't, and never a reason to lose them: Codex 0.158's + # app-server can leave it unanswered for good. + if not plan: + try: + account_msg = rpc_request(proc, 3, "account/read", timeout=2) + account = (account_msg.get("result") or {}).get("account") or {} + plan = account.get("planType") or account.get("type") or "" + except TimeoutError: + pass result["tierLabel"] = str(plan) if plan else "" for window in (limits.get("primary"), limits.get("secondary")): diff --git a/test/shell.d/agent-usage-codex-scanner-test.sh b/test/shell.d/agent-usage-codex-scanner-test.sh index 3ce4883c..4b0b7fb6 100644 --- a/test/shell.d/agent-usage-codex-scanner-test.sh +++ b/test/shell.d/agent-usage-codex-scanner-test.sh @@ -26,10 +26,12 @@ while read -r request; do jq -cn --argjson id "$id" '{id: $id, result: {}}' ;; account/read) - jq -cn --argjson id "$id" '{id: $id, result: {account: {}}}' + # Codex 0.158 can leave this one unanswered for good. + [[ -n ${CODEX_ACCOUNT_READ_HANGS:-} ]] || + jq -cn --argjson id "$id" '{id: $id, result: {account: {}}}' ;; account/rateLimits/read) - jq -cn --argjson id "$id" '{id: $id, result: {rateLimits: {}}}' + jq -cn --argjson id "$id" --argjson limits "${CODEX_RATE_LIMITS:-{\}}" '{id: $id, result: {rateLimits: $limits}}' ;; esac done @@ -603,3 +605,14 @@ result=$(HOME="$INTERRUPTED_HOME" CODEX_HOME="$INTERRUPTED_HOME/.codex" XDG_CACH [[ $(jq -r '.todayTotalTokens' <<<"$result") == "9" ]] || fail "Codex collector does not reuse a snapshot from an interrupted scan" "$result" pass "Codex collector does not cache an interrupted opencode scan" + +# The limits name the plan themselves, so an account/read that never answers +# costs nothing: the limits still arrive, and quickly. +started=$(date +%s) +result=$(HOME="$TEST_HOME" CODEX_HOME="$TEST_HOME/.codex" XDG_DATA_HOME="$TEST_HOME/.local/share" PATH="$TEST_HOME/bin:$PATH" \ + CODEX_ACCOUNT_READ_HANGS=1 CODEX_RATE_LIMITS='{"planType":"pro","primary":{"usedPercent":36,"windowDurationMins":10080}}' \ + "$ROOT/bin/omarchy-agent-usage-codex" --limits-only) +(( $(date +%s) - started < 4 )) || fail "Codex collector doesn't wait on account/read when the limits name the plan" +[[ $(jq -c '{tierLabel, usageStatusText, limits: [.limits[] | {label, percent}]}' <<<"$result") == '{"tierLabel":"pro","usageStatusText":"","limits":[{"label":"Weekly (7-day)","percent":0.36}]}' ]] || + fail "Codex collector reads limits even when account/read never answers" "$result" +pass "Codex collector reads limits even when account/read never answers"