Skip to content

feat(llm): Sign in with ChatGPT — OAuth device flow (#294) - #823

Merged
Weegy merged 7 commits into
mainfrom
feat/294-chatgpt-oauth
Aug 21, 2026
Merged

feat(llm): Sign in with ChatGPT — OAuth device flow (#294)#823
Weegy merged 7 commits into
mainfrom
feat/294-chatgpt-oauth

Conversation

@Weegy

@Weegy Weegy commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Closes #294 (4b, experimental).

Summary

Connect a ChatGPT subscription as an LLM provider via an OAuth device login (no API key), driving the ChatGPT/Codex Responses backend over SSE. Gated behind CHATGPT_SUBSCRIPTION_EXPERIMENTAL (off by default) — driving programmatic calls through a consumer subscription is a ToS grey area, so the connect modal shows a prominent notice and it is not an enterprise feature.

The gating question, settled empirically

The issue asked whether a ChatGPT-subscription token can speak Chat Completions. Tested live with a real subscription token:

  • api.openai.com/v1/chat/completions401 (model.request scope missing)
  • api.openai.com/v1/responses401 (api.responses.write missing)
  • chatgpt.com/backend-api/codex/responses200 SSE — and custom function tools, forced tool_choice, parallel tool calls and vision all work.

So 4b needs a Responses-API adapter targeting the Codex backend, not a baseURL switch. The prototype's RFC 8628 endpoints were also wrong (/oauth/device/code → 404); the device flow was rewritten to the real 3-step protocol (/api/accounts/deviceauth/usercode → poll /deviceauth/token → authorization-code exchange, PKCE server-side), verified against auth.openai.com and the open-source openai/codex CLI.

What landed

  • Contract @omadia/llm-provider-api → 1.1.0 (additive): wire format openai-responses, descriptor oauth, adapter bearerProvider.
  • New @omadia/llm-adapter-openai-responses: SDK-free SSE adapter (own parser), neutral↔Responses mapping, stream/complete, error classification. Forces stream:true/store:false, sends the experimental headers, resolves a fresh bearer per request.
  • providerOAuthTokenStore: process-wide, single-flight refresh; rotated tokens (rotation + reuse-detection are real) fan out to all three LLM vault scopes newest-wins; a dead grant parks the provider in reconnect_required (surfaced as no_key → "Reconnect") instead of retry-hammering.
  • Connect routes POST /api/v1/admin/providers/oauth/{start,poll} (device secret never leaves the server; expired-flow sweep + in-flight cap), oauthConnect DTO + status branch.
  • openai-chatgpt provider in EXPERIMENTAL_LLM_PROVIDERS, registered only under the flag.
  • web-ui: ChatGptConnectModal (device code + verification link + ToS notice + poll loop) + panel connect button, i18n en+de.

Review

An omadia-reviewer pass flagged two HIGH bugs (rotation-persistence race, dead-grant classification via unset Error.name) and MEDIUM items (hydration race, latch not surfaced, unbounded pending-flow map) — all fixed in the second commit with regression tests.

Test plan

  • middleware: typecheck + lint clean; 166/166 provider/adapter/oauth suites green (device flow, token store single-flight/rotation/persist-throw/reconnect, SSE adapter, connect routes)
  • web-ui: typecheck + lint (0 errors) + i18n:check + vitest (modal 3/3, i18n parity 10/10)
  • live: device-code endpoints + Responses backend probed with a real subscription; codex npm install verified in node:22-slim (separate PR feat(admin): runtime install of subscription CLIs from the admin UI #816)
  • CI green (middleware / web-ui / schema / audit)
  • Manual e2e (needs a real subscription; documented): connect → assign to a plugin → chat turn on the OAuth bearer

⚠️ Experimental / ToS grey area — env-gated off, prominent in-UI notice; not marketed as enterprise.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Weegy added 7 commits August 21, 2026 09:37
… (4b, experimental)

EXPERIMENTAL / ToS grey area. Adds the pure, tested token-lifecycle core for
connecting a provider via a ChatGPT subscription instead of an API key (RFC 8628
device grant): requestDeviceCode / pollDeviceToken / refreshAccessToken /
isAccessTokenExpired against the OpenAI Codex public client, plus vault storage
(provider:<id>/oauth_*) read/write helpers. All functions take an injected fetch
+ clock so the lifecycle is unit-tested without the live endpoints.

NOT wired yet (left for follow-up + live verification): binding the OAuth bearer
into resolveLlmProvider with the correct base URL (the ChatGPT token's audience
is OpenAI's Codex/Responses backend, not standard Chat Completions — may need a
Responses shim), the web-UI connect button + ToS notice, and a real login test.

Forge GPT-5.4 security review: FIX-APPLIED — fixed a token-leak (untrusted error
text reflected into Error.message) and a stale-token persistence bug, +2 tests.
No token leakage confirmed. 15/15 tests, build + typecheck green.
…er (#294)

Finishes 4b: connect a ChatGPT subscription as an LLM provider via the real
device-code login (no API key), driving the ChatGPT/Codex Responses backend.

Empirically settled the issue's gating question with a live subscription token:
- api.openai.com/v1/chat/completions and /responses both 401 (wrong audience)
- chatgpt.com/backend-api/codex/responses returns 200 SSE; tools, forced
  tool_choice, parallel tool calls and vision all work.
The prototype's RFC 8628 endpoints were wrong (404); rewrote oauthDeviceFlow.ts
to the real 3-step flow (usercode -> poll -> authorization-code exchange, PKCE
server-side) verified against auth.openai.com + the open-source codex CLI.

- packages/llm-provider-api -> 1.1.0: wireFormat 'openai-responses', descriptor
  .oauth, adapter .bearerProvider (all additive)
- new @omadia/llm-adapter-openai-responses: SDK-free SSE adapter (own parser),
  neutral<->Responses mapping, stream/complete, error classification
- providerOAuthTokenStore: process-wide, single-flight refresh, rotation fanned
  out to all LLM vault scopes newest-wins, terminal reuse -> reconnect_required
- adminProviders: POST /oauth/{start,poll}, oauthConnect DTO + status branch
- openai-chatgpt provider gated behind CHATGPT_SUBSCRIPTION_EXPERIMENTAL (off)
- web-ui: ChatGptConnectModal (device code + ToS notice) + panel connect button,
  i18n en+de
- tests: device-flow, token store (single-flight/rotation/reconnect), SSE
  adapter, connect routes, connect modal

Experimental / ToS grey area — env-gated off, prominent in-UI notice.
…ation (#294)

- token store: commit rotated tokens to memory BEFORE persisting, and degrade a
  persist failure to a logged warning — a transient vault write no longer drops
  a live token and wedges the grant into reconnect_required (HIGH)
- OAuthReconnectRequiredError sets .name, so the openai-responses adapter's
  cross-package name-match classifies a dead grant as auth, not other (HIGH)
- token store hydration is a shared promise, not a bool — a second cold caller
  awaits the same load instead of racing past a half-populated entry (MEDIUM)
- adminProviders GET: an OAuth provider in reconnect_required reports no_key so
  the row steers to "Reconnect" instead of a green chip that lies (MEDIUM)
- /oauth/start sweeps expired pending flows and caps in-flight flows at 32,
  so a closed modal / StrictMode double-mount can't leak entries (MEDIUM)
- web-ui pollProviderOAuth maps 404/502 back to expired/error terminal states
  instead of a generic failure (LOW)
- tests: persist-throw keeps the rotated token, slow-hydration concurrency, and
  dead-grant classified as auth
# Conflicts:
#	docs/CHANGELOG.md
#	middleware/.env.example
)

The errorHelpCoverage guard requires what+next copy in both locales for every
code a covered route emits. The 4 new adminProviders oauth codes
(oauth_{start,poll}_failed, oauth_too_many_flows, oauth_unsupported) shipped
without it — CI web-ui vitest caught it. Adds the copy + ln entries.
@Weegy
Weegy merged commit 747e9e0 into main Aug 21, 2026
8 checks passed
@Weegy
Weegy deleted the feat/294-chatgpt-oauth branch August 21, 2026 09:00
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.

4b (experimental): finish 'Sign in with ChatGPT' OAuth connect flow

1 participant