Skip to content

Drive OAuth sign-in off the provider registry - #397

Draft
dannon wants to merge 5 commits into
galaxyproject:mainfrom
dannon:feat/generic-oauth-providers
Draft

Drive OAuth sign-in off the provider registry#397
dannon wants to merge 5 commits into
galaxyproject:mainfrom
dannon:feat/generic-oauth-providers

Conversation

@dannon

@dannon dannon commented Aug 3, 2026

Copy link
Copy Markdown
Member

Stacked on #396 -- merge that first. This branch contains #396's four commits
plus one on top; only the last
commit
is this PR's
work. It needs the provider-carried auth that arrived with pi 0.81, which is why
it can't stand alone.

Since pi 0.81 each provider carries its own auth.oauth, which means "does this
provider support sign-in" is a question the registry can answer. We were
answering it from three hardcoded sets instead -- oauth-handler.ts,
agent.ts, and the renderer -- all reading {"openai-codex"}, all silently
wrong the moment pi adds a provider. Which it has, repeatedly.

oauth-handler now reads the list off ModelRuntime.getProviders() once at
startup and everything else asks it. signInOpenAICodex() becomes
signInOAuth(provider), which works for any of them because the flow belongs to
the provider rather than to us. On this pi that's seven -- anthropic,
github-copilot, kimi-coding, openai-codex, openrouter, radius, xai
-- where before only openai-codex was reachable.

Worth knowing about two of them. github-copilot is the interesting one: pi 0.83
adds Claude Opus 5 on Copilot with adaptive thinking and a 1M window, so a
Copilot seat becomes a real route to Opus 5. anthropic is the one to be
skeptical of -- an earlier investigation found pi's Anthropic OAuth is metered as
a third-party harness and can't fund Loom off a Claude Pro/Max subscription. This
PR makes it reachable; it doesn't make it useful, and someone should confirm
that's still true before we point anyone at it.

Shape of it

The registry read is async and several callers aren't, so it's primed at startup
behind a seed list rather than awaited. The seed is load-bearing: without it a
status check landing before priming completes reports "not an OAuth provider",
and the UI offers an API-key field for an account that hasn't got one. An empty
read leaves the seed in place too -- that means the registry is unhappy, not that
sign-in stopped existing.

Button text now comes from the provider's own login label, so it reads "Sign in
with GitHub Copilot" rather than "Sign in with ChatGPT" for all seven.
Device-code flows open the verification page and log the code; Orbit has no UI to
display one yet, and that beats looking hung. The paste-back prompt explains
itself instead of rejecting with callback-port advice that only ever made sense
for Codex.

Verification

Root and app typecheck plus the full 1364-test suite pass, format and lint clean.

Not live-eyeballed, and this one really wants it. It's UI wiring plus six
newly-reachable auth flows, none of which I clicked. At minimum someone should
run the Codex flow to confirm no regression, then one new provider end to end.
The device-code path (Kimi, possibly others) is the least certain -- it's coded
from the event type, not from watching it run.

No new unit tests: the moving parts are an Electron-coupled main-process module
and renderer DOM wiring, neither of which the existing test seams reach.
Extracting a DOM-free label/provider-map helper would be the way in if we want
coverage.

dannon added 5 commits August 3, 2026 09:41
Straight `npm update` on root and app -- no package.json ranges changed, just
the pins catching up. Notable movers are prettier 3.8.4 to 3.9.6, eslint 10.4.1
to 10.8.0, vitest 4.1.8 to 4.1.10, better-sqlite3 12.10.0 to 12.11.1, dompurify
3.4.9 to 3.4.12, and electron 41.7.1 to 41.10.3 on the app side. Root typecheck,
app typecheck, and the full test suite are all green on these.

The prettier bump wants to reformat some files; that's the next commit so this
one stays a pure dependency change.
Mechanical only -- prettier 3.9.6 lays out a few union types and long
expressions differently than 3.8.4 did. No behavior change anywhere, and the
whole tree passes format:check again. Kept out of the lockfile commit so a
future bisect doesn't have to read 21 files of noise to find a dependency move.
Bumps pi-coding-agent/pi-tui/pi-ai to 0.83.0, pi-mcp-adapter to ^2.18.0, and
pi-web-access to ^0.17.1. These go together: pi-web-access 0.17 declares peer
deps on all three pi packages, and pi-mcp-adapter needed the lockfile moved off
2.9.0 so dev finally resolves what users actually install. Also drops the
@mozilla/readability override -- pi-web-access depends on ^0.6.0 itself now, so
it was doing nothing.

Four things broke along the way. pi 0.80 moved pi-ai's global API off the
package root to /compat; the extension loader aliases the root back, which
covers extensions/loom/teams but not Orbit's main process, so ipc-handlers was
heading for undefined getProviders/getModels at runtime rather than a compile
error. pi 0.83 stopped re-exporting AuthStorage from the package root, and
reshaped ModelRegistry to take a ModelRuntime built by an async factory instead
of an AuthStorage plus a path -- that hit both --list-models and the
custom-provider test. The test now stands up a small CredentialStore stub rather
than reaching for pi internals, and --list-models drops to just listModels(),
which takes a ModelRuntime directly now.

The interesting one is OAuth. pi 0.81 folded the per-service loginOpenAICodex()
helper into the provider: auth hangs off provider.auth.oauth as a
login/refresh/toAuth triple, and driving login is the app's job now. That reads
like a removal from the outside -- /oauth is a type-only entry point since 0.81
and the implementation sits behind an unexported path -- but the supported route
is just to ask a ModelRuntime for the provider, which is what signInOpenAICodex
does now.

Verified with root and app typecheck, the full test suite, and smoke:pack, which
installs the packed tarball without the lockfile and boots the CLI.
The picker, pricing table, and context windows in the renderer are only
fallbacks -- populateDynamicModelData() overwrites them from pi's registry at
startup -- but they'd drifted far enough to be actively misleading, topping out
at Opus 4.8 and still listing GPT-4o and o1 on the OpenAI side. The o1 and
gpt-4-turbo entries were the worst of it: loom's own legacy denylist strips
those from the dynamic list, so the fallback was offering models the live path
deliberately hides.

Adds Opus 5, Sonnet 5, and Fable 5, refreshes OpenAI to the gpt-5.x line and
Google to gemini-3.x, and fixes the Gemini 2.5 Flash label, which claimed
$0.15/$0.60 against an actual $0.30/$2.50. Every number here is read out of the
0.83 registry rather than hand-copied. Sonnet 5's $2/$10 is introductory
pricing, so it gets a comment pointing at the registry as the real source.

Also teaches shortModelLabel about the fable family -- without it the footer
renders a raw "claude-fable-5" where every other model gets a friendly name.

No change needed in exec-guard's tier classifier, which I'd initially suspected:
Opus 5 and Sonnet 5 match the existing opus/sonnet markers, and Fable 5 and
Gemini 3.x Pro both clear the trusted price threshold on their own.
Since pi 0.81 each provider carries its own auth, so "which providers support
sign-in" is a question the registry can answer. We were answering it from three
separate hardcoded sets -- one in oauth-handler, one in agent, one in the
renderer -- all reading {"openai-codex"}, and all of them wrong the moment pi
adds a provider.

Now oauth-handler reads the list off ModelRuntime.getProviders() once at startup
and everything else asks it. signInOpenAICodex becomes signInOAuth(provider),
which works for any of them because the login flow is the provider's, not ours.
On this pi that's seven: anthropic, github-copilot, kimi-coding, openai-codex,
openrouter, radius, and xai, where before only openai-codex was reachable.

The registry read is async and a few callers aren't, so it's primed at startup
behind a seed list rather than awaited. The seed matters: without it, a status
check that lands before priming finishes would report "not an OAuth provider"
and the UI would offer an API-key field for an account that hasn't got one. An
empty read leaves the seed alone too, since that means the registry is unhappy,
not that sign-in stopped existing.

The renderer's button text comes from the provider's own login label now, so it
reads "Sign in with GitHub Copilot" instead of "Sign in with ChatGPT" for every
provider. Device-code flows get the verification page opened and the code
logged -- Orbit has no UI to display one yet, and that's better than looking
hung. Same for the paste-back prompt, which now explains itself instead of
rejecting with callback-port advice that only made sense for Codex.

Root and app typecheck and the full suite pass. Not live-eyeballed: this is UI
wiring, and the new providers each want a real click-through before anyone
trusts them.
@dannon dannon mentioned this pull request Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant