Visitar URL original
feat(gui): live routing tab: see which account answered, why others were skipped, and where the time went by andrew05060414 · Pull Request #8730 · lidge-jun/opencodex · GitHub
Skip to content

feat(gui): live routing tab: see which account answered, why others were skipped, and where the time went - #8730

Closed
andrew05060414 wants to merge 8 commits into
lidge-jun:devfrom
andrew05060414:feat/route-live-flow
Closed

andrew05060414 wants to merge 8 commits into
lidge-jun:devfrom
andrew05060414:feat/route-live-flow

Conversation

@andrew05060414

@andrew05060414 andrew05060414 commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The problem. When a request goes through OpenCodex there is no single place to see how it was routed: which account or combo target answered, why the other accounts were passed over, where the time went. The Logs page has the raw rows, but the reasoning is spread across routeDecision, attempts[] and account state that is never recorded at all.

What changes. The Models page gets a Live routing tab (#models/live), modeled on the routing page of Magpie (MIT, © yetone; ported logic carries the notice). For the latest request, or one selected in the list, it shows:

  • The route: client → OpenCodex → every candidate. The account or combo target that answered, the ones that failed (HTTP status and reason), and the ones skipped before any send, each with a plain reason. A one-line outcome ("Tried 2 times; Personal answered last") and a numbered "how this request was routed" list explain it step by step.
  • Where the time went: OpenCodex internal time, earlier failed attempts, the wait for the first token, and streaming. Every segment is a measured field; there is no DNS/TLS split because Bun's fetch does not expose one.
  • Recent requests, grouped by request, session or API key, with account, model, effort, duration, time to first token, tokens, cache-hit %, tok/s and estimated cost. A value that is unknown shows a dash, never zero.

Combos and accounts keep their own pages; the tab links to them (Models › Combos, and the provider's Accounts tab with a one-line pool summary).

Who notices. Anyone running several OAuth accounts or combos can now answer "why did this request land on that account?" without reading JSON.

Backend (small, in-memory only)

  • accountRoster on the final request row: for a request served by a multi-account generic OAuth provider, each account's hashed o-label and its state when the request finished (available, paused, needs-reauth, cooldown, quota-exhausted, model-unlisted), classified with the same checks the selector uses. Registered by the OAuth layer into a slot (src/server/request-log-route-live.ts), so request-log.ts does not import src/oauth, and an install that never loads OAuth pays one null check. The gate is the served o-label, which needs no config read.
  • upstreamSendMs (request start to the final attempt's start) and an optional privacyRedactionMs, so the time bar is built from measurements only.
  • GET /api/oauth/accounts returns the same logLabel per generic OAuth account, so the dashboard can show the account's display label for a roster entry.
  • None of these fields reach usage.jsonl: the persistence projection rebuilds its row field by field and does not copy them. No bodies, account ids, emails or secrets are recorded.
  • request-log.ts grows by 3 lines (1983 → 1986, cap 2000); everything else lives in new sibling files. No new import reaches src/lab/ from the core path.

A stacked follow-up PR (#8731) adds the context-window breakdown (system prompt, tools, skills, memory files, files, tool results, per-turn) on top of this.

Screenshots

Local test instance with a fake upstream: four OAuth accounts (one rate-limited, one paused, one needing sign-in) and a failover combo. Account labels are aliases from the fake store.

Live routing, English

(The screenshot was taken on the stacked branch, so it also shows the context-window card from the follow-up PR.)

Verification

  • bun run typecheck, bun run structure:check, bun run privacy:scan: pass.
  • Focused backend tests: tests/usage/request-log-account-roster.test.ts, tests/usage/request-log-route-live-timing.test.ts, tests/oauth/oauth-accounts-log-label.test.ts, tests/usage/request-log.test.ts, tests/lab/core-lab-boundary.test.ts, tests/ci-workflows/file-size-ratchet.test.ts, tests/test-layout.test.ts, tests/gui/models-workspace-tabs.test.ts: pass.
  • GUI: cd gui && bun test tests/route-live* tests/fr-localization.test.ts tests/locale-parity.test.ts tests/models-workspace-panels.test.tsx tests/compatibility-lab.test.tsx tests/models-keep-native-v1-placement.test.ts (includes new route-live-model, route-live-time, route-live-render), bun run lint, bun run lint:i18n, tsc --noEmit, bun run build: pass.
  • Manual: ran the proxy from this branch with a fake OpenAI-compatible upstream and sent Responses, Chat Completions and Messages requests (account 429 failover, paused and needs-sign-in accounts, combo failover); the tab showed each case as described above.
  • Not run locally: the full bun run test suite. Several sessions were running suites on the same Windows machine, so full-suite timeouts there were load, not signal; and the test preload's git-config guard rejects GIT_CONFIG_GLOBAL=NUL with this machine's git 2.56, failing the first case of every file and a handful of test-layout-tooling cases on unmodified dev as well. Focused files ran with OCX_GIT_CONFIG_GUARD_CHECKOUT pointing at a directory without .git. A local GUI suite run showed no failures in files this PR touches beyond one French-copy case, which is fixed here. Full coverage is left to CI.

Readiness (2026-11-13)

  • Branch is 2 commit(s) behind dev (873ec9b), inside the gate's window; no rebase for review.
  • No Codex or CodeRabbit findings are open on this PR (CodeRabbit skipped the draft; there are no inline review comments).
  • Also merged into a local integration build with the other open PRs of this series and deployed on a real install. The combined build passed bun run typecheck, structure:check, skill:surface:check, privacy:scan, the layout and file-size guards and this PR's focused tests; after the deploy one real request each through Claude, GPT, Gemini, DeepSeek and Grok returned 200 and the error rate did not rise.
  • Still needs the maintainer-sponsored label: the hygiene gate flags src/oauth/generic-account-failover.ts and src/server/management/oauth-account-routes.ts. Everything else is ready for review.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed. (docs-site/.../guides/model-routing.md, "Watching routing live")
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. (Only hashed account labels and sizes are recorded; /api/oauth/accounts gains a non-secret hashed label.)

Review readiness checklist

This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:

  • Required local validation passed; commands, results, and any full-suite exception are documented.

  • I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).

  • I resolved all correct Codex and CodeRabbit findings.

  • My PR is ready for review.

…he final request row

The final request log of a request served by a multi-account generic OAuth
provider now carries accountRoster: each account's hashed o-label and whether
it was available, paused, needing re-auth, cooling down, out of quota, or not
listing the model. It stays in memory for /api/logs and never reaches
usage.jsonl. /api/oauth/accounts returns the same logLabel per account so the
dashboard can name the account a roster label belongs to.
… long privacy redaction took

Adds upstreamSendMs (request start to the final attempt's start, which
includes earlier failed attempts) and an optional privacyRedactionMs to the
in-memory request row, so the live routing view can split a request's time
into measured phases without inventing DNS or TLS numbers.
Models gains a Live routing tab (#models/live) that polls /api/logs while
visible. For the latest or a pinned request it shows client -> OpenCodex ->
candidates, marking the account or target that answered, the ones that
failed with their status and reason, and the ones skipped with the reason the
route decision or the account pool recorded. An outcome sentence and a step
list explain the route. The request list groups by request, session or key
and shows account, model, effort, duration, time to first token, tokens,
cache-hit rate, tok/s and estimated cost; unknown values show a dash, never
zero. Combos and accounts keep their own pages and are linked.
The live routing view gains a time bar for the pinned or latest request:
privacy redaction, OpenCodex internal time, earlier failed attempts, the wait
for the upstream's first token, and streaming. Every segment comes from a
measured field; a request without timing says so instead of drawing a bar,
and no DNS or TLS split is shown because Bun's fetch does not expose one.
…nly in the live routing view

A combo row showed the target it landed on as what the client asked for; it
now shows the combo. An API-key account reads as its provider plus a short
fingerprint instead of a 33-character digest, and the explicit-provider,
alias, rule and native route reasons have plain sentences.
@coderabbitai

coderabbitai Bot commented Nov 14, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Deterministic hygiene checks failed.

  • unsponsored_surface — This changes an authentication, workflow, release-automation, or dependency surface. MAINTAINERS.md requires security review for these; ask a maintainer to apply maintainer-sponsored once they have reviewed it. Paths: src/oauth/generic-account-failover.ts, src/server/management/oauth-account-routes.ts.

@github-actions github-actions Bot added the enhancement New feature or request label Nov 14, 2026
@github-actions

github-actions Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • hygiene: unsponsored_surface.

What to do

  • Fix unsponsored_surface — This changes an authentication, workflow, release-automation, or dependency surface. MAINTAINERS.md requires security review for these; ask a maintainer to apply maintainer-sponsored once they have reviewed it. Paths: src/oauth/generic-account-failover.ts, src/server/management/oauth-account-routes.ts.

Review readiness checklist

  • ✅ Required local validation passed; commands, results, and any full-suite exception are documented.
  • ✅ I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).
  • ✅ I resolved all correct Codex and CodeRabbit findings.
  • ✅ My PR is ready for review.

✅ 4/4 boxes ticked.

This pull request was already a draft. Its draft status will be preserved after every issue above is resolved.

Hygiene

⚠️ Deterministic hygiene checks failed.

  • unsponsored_surface — This changes an authentication, workflow, release-automation, or dependency surface. MAINTAINERS.md requires security review for these; ask a maintainer to apply maintainer-sponsored once they have reviewed it. Paths: src/oauth/generic-account-failover.ts, src/server/management/oauth-account-routes.ts.

@andrew05060414

Copy link
Copy Markdown
Contributor Author

Superseded by #8741, which carries this change together with the related live routing PRs as one commit each on current dev, with no stacking. The design notes in this description still apply.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request intake: hygiene-blocked Deterministic PR hygiene checks failed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant