feat(desktop): make xAI Grok a first-class OAuth provider in the launcher - #37697
Conversation
…cher xAI Grok was only reachable via the "I have an API key" form. xAI's OAuth (SuperGrok / Premium+) flow already exists in the backend (`hermes auth add xai-oauth`) but was never surfaced in the desktop onboarding launcher. Add a loopback PKCE flow: the local backend binds the 127.0.0.1 callback listener, the client opens the browser, and the redirect lands back automatically — no code to copy/paste. Reuses the existing xAI OAuth helpers (discovery, callback server, token exchange, persist) rather than duplicating them. - web_server: catalog entry (flow: loopback) + status dispatch + _start_xai_loopback_flow + background worker + route branch - desktop: 'loopback' flow type, awaiting_browser status, xAI Grok card (PROVIDER_DISPLAY / FLOW_SUBTITLES / FlowPanel waiting render) - tests: catalog listing, start authorize-url, worker persist, state mismatch rejection
🔎 Lint report:
|
There was a problem hiding this comment.
Pull request overview
Promotes xAI Grok OAuth to a first-class onboarding provider in the desktop launcher by adding a new loopback PKCE flow to the local FastAPI backend and wiring the desktop UI/store to open the browser and poll session status until completion.
Changes:
- Backend: add
xai-oauthto the OAuth provider catalog and implement loopback PKCE start + background worker to complete token exchange and persistence. - Desktop: extend OAuth flow typing/state machine with
loopback+awaiting_browser, open the authorize URL, and poll the session until approved/error. - Tests: add coverage for provider catalog listing, loopback start response, successful persistence, and state-mismatch rejection.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
tests/hermes_cli/test_web_oauth_dispatch.py |
Adds unit tests for xAI loopback flow listing/start/worker success + state mismatch. |
hermes_cli/web_server.py |
Adds xai-oauth provider entry, status dispatch, loopback flow implementation, and start route wiring. |
apps/desktop/src/types/hermes.ts |
Adds loopback flow and start-response typing. |
apps/desktop/src/store/onboarding.ts |
Adds awaiting_browser state and generalizes polling to support loopback sessions. |
apps/desktop/src/components/desktop-onboarding-overlay.tsx |
Adds xAI provider card ordering/title and a waiting panel for loopback flow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- web_server: join the callback-server thread in the start error path so a failed discovery/URL build doesn't leave a daemon thread running - web_server: loopback worker now bails if the session was cancelled while waiting for the callback or exchanging the code, instead of persisting tokens the user no longer wants (+ regression test) - onboarding: fall back to window.open when the desktop bridge's openExternal is unavailable, so the flow never silently stalls
- onboarding: openSignInUrl now falls back to window.open when the desktop bridge's openExternal throws/rejects (OS handler missing, user denied), not just when the bridge is absent - web_server: cancelling a loopback session shuts down the 127.0.0.1 callback server + joins its thread immediately, freeing the port instead of holding it until the wait times out (+ regression test) - web_server: document the new "loopback" flow in the /api/providers/oauth enum, the poll-endpoint docstring, and the Phase 2 flow comment block
source_label is meant to be a human-readable origin (file path / source),
not the internal auth_mode string ("oauth_pkce"). Surface the auth-store
path, then the source slug, then a generic label.
Shutting down the callback server stopped the serve thread but left the worker spinning in _xai_wait_for_callback (which polls callback_result) until the timeout. Flag callback_result as cancelled on DELETE so the wait returns promptly and the daemon thread exits — avoids thread buildup on repeated cancel/retry.
…der-desktop feat(desktop): make xAI Grok a first-class OAuth provider in the launcher
feat(desktop): make xAI Grok a first-class OAuth provider in the launcher
…der-desktop feat(desktop): make xAI Grok a first-class OAuth provider in the launcher
…der-desktop feat(desktop): make xAI Grok a first-class OAuth provider in the launcher
…der-desktop feat(desktop): make xAI Grok a first-class OAuth provider in the launcher
…der-desktop feat(desktop): make xAI Grok a first-class OAuth provider in the launcher
…der-desktop feat(desktop): make xAI Grok a first-class OAuth provider in the launcher
Summary
xAI Grok was only reachable through the desktop's "I have an API key" form, even though a full xAI OAuth (SuperGrok / Premium+) flow already exists in the backend (
hermes auth add xai-oauth). This promotes it to a first-class one-click provider card in the onboarding launcher, alongside Nous / Anthropic / OpenAI Codex / MiniMax.It introduces a loopback PKCE flow: the desktop's local Python backend binds the
127.0.0.1callback listener, the client opens the browser, and the redirect lands back automatically — nothing to copy or paste. The implementation reuses the existing xAI OAuth helpers (discovery, callback server, token exchange, persist) rather than duplicating them.Backend (
hermes_cli/web_server.py)xai-oauthadded to_OAUTH_PROVIDER_CATALOGwithflow: "loopback"(now listed by/api/providers/oauth)auth.get_xai_oauth_auth_status()so the card shows Connected_start_xai_loopback_flow()+_xai_loopback_worker()(waits for callback, validates state, exchanges code, persists to auth store + credential pool — mirrorshermes auth add xai-oauth)loopbackbranch wired into thestart_oauth_loginroute; disconnect works via the genericclear_provider_authDesktop (
apps/desktop)types/hermes.ts:'loopback'flow +LoopbackStartresponse shapestore/onboarding.ts: newawaiting_browserstatus;startProviderOAuthopens the auth URL and polls (generalizedpollDevice→pollSession)desktop-onboarding-overlay.tsx: "xAI Grok" card title,loopbacksubtitle, and a waiting panelNotes
XAI_API_KEYpath still lives in the API-key form (same split as Anthropic / OpenAI Codex)._OAUTH_PROVIDER_CATALOG+ hardcodedAPI_KEY_OPTIONSsplit is the thing to collapse in the follow-up that feeds the selector dynamically from/providers.Test plan
tsc -btypecheck clean; ESLint clean on edited files (repo-wide lint errors are pre-existing)tests/hermes_cli/test_web_oauth_dispatch.py(12),test_web_server.py(211),test_auth_xai_oauth_provider.py(82),test_xai_oauth_pkce_token_exchange.py(14) all pass