4b (experimental): finish "Sign in with ChatGPT" OAuth connect flow
Part of the pluggable-LLM-provider feature (PR #292). The api-key path for OpenAI (incl. GPT-5.x) is shipped and live-verified. 4b — connecting a provider via a ChatGPT subscription (OAuth device flow) instead of an API key — is deliberately excluded from #292 because its end-to-end functionality hinges on an unresolved question (below). This issue tracks finishing it.
⚠️ Experimental / ToS grey area. Using a ChatGPT-subscription login to drive programmatic API calls is a grey area of OpenAI's terms — surface a clear notice in the UI and do not market it as an enterprise feature.
Prototype already written (for reference)
The pure, unit-tested core exists on branch backup/llm-provider-oauth-4b (commits 3e6691c + e966bd9), removed from #292:
packages/llm-provider/src/oauthDeviceFlow.ts — RFC 8628 device grant: requestDeviceCode / pollDeviceToken / refreshAccessToken / isAccessTokenExpired against the OpenAI Codex public client (app_EMoamEEZ73f0CkXaXp7hrann, token endpoint auth.openai.com/oauth/token, verify auth.openai.com/codex/device). All take an injected fetch + clock.
providerCredentials.ts — vault token storage (provider:<id>/oauth_*) + resolveProviderOAuthBearer (auto-refresh + persist).
providerFactory.ts — resolveLlmProvider prefers the OAuth bearer when configured, else the api-key path (byte-identical).
- 19 mocked-HTTP unit tests; Forge-reviewed (fixed a token-leak + a stale-token bug).
The gating question (must be answered first)
A "Sign in with ChatGPT" token's audience is OpenAI's Codex/Responses backend, not necessarily the standard Chat Completions API our openaiProvider adapter speaks.
Test to settle it (~2 min):
codex login --device-auth # auth.openai.com/codex/device, enter code; token → ~/.codex/auth.json
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer <access_token>" -H "content-type: application/json" \
-d '{"model":"gpt-5.4","messages":[{"role":"user","content":"ping"}],"max_completion_tokens":5}'
- 200 → token speaks Chat Completions → 4b = connect-UI + correct
baseURL. Small.
- 401 / wrong audience → token speaks only the Codex/Responses backend → 4b needs a Responses-API adapter (separate, larger slice). Also confirm the exact device-authorization endpoint (the prototype's
deviceAuthorizationEndpoint is best-effort).
Remaining work (once the audience is known)
- Re-land the prototype core from
backup/llm-provider-oauth-4b.
- Backend connect route:
POST /api/v1/admin/providers/oauth/{start,poll} — start → device code + verification URL; poll → on success writeProviderOAuthTokens into the right SETTING_DEFS multi-scope set + reactivate. (Per-plugin credential scoping mirrors the api-key path.)
- Admin-UI "Sign in with ChatGPT" button + device-code modal + clear ToS notice (i18n en+de).
- If audience ≠ Chat Completions: a Responses-API adapter (or the correct
baseURL).
- End-to-end test: connect → assign a plugin to the oauth path → a chat turn runs on the OAuth bearer.
Test levels (summary)
- Unit (done on the backup branch):
node --import tsx --test test/llmProviderOAuth*.test.ts — 19/19.
- Audience (manual, above) — the decision gate.
- E2E — after the connect UI/route + (maybe) Responses adapter land.
4b (experimental): finish "Sign in with ChatGPT" OAuth connect flow
Part of the pluggable-LLM-provider feature (PR #292). The api-key path for OpenAI (incl. GPT-5.x) is shipped and live-verified. 4b — connecting a provider via a ChatGPT subscription (OAuth device flow) instead of an API key — is deliberately excluded from #292 because its end-to-end functionality hinges on an unresolved question (below). This issue tracks finishing it.
Prototype already written (for reference)
The pure, unit-tested core exists on branch
backup/llm-provider-oauth-4b(commits3e6691c+e966bd9), removed from #292:packages/llm-provider/src/oauthDeviceFlow.ts— RFC 8628 device grant:requestDeviceCode/pollDeviceToken/refreshAccessToken/isAccessTokenExpiredagainst the OpenAI Codex public client (app_EMoamEEZ73f0CkXaXp7hrann, token endpointauth.openai.com/oauth/token, verifyauth.openai.com/codex/device). All take an injectedfetch+ clock.providerCredentials.ts— vault token storage (provider:<id>/oauth_*) +resolveProviderOAuthBearer(auto-refresh + persist).providerFactory.ts—resolveLlmProviderprefers the OAuth bearer when configured, else the api-key path (byte-identical).The gating question (must be answered first)
A "Sign in with ChatGPT" token's audience is OpenAI's Codex/Responses backend, not necessarily the standard Chat Completions API our
openaiProvideradapter speaks.Test to settle it (~2 min):
baseURL. Small.deviceAuthorizationEndpointis best-effort).Remaining work (once the audience is known)
backup/llm-provider-oauth-4b.POST /api/v1/admin/providers/oauth/{start,poll}—start→ device code + verification URL;poll→ on successwriteProviderOAuthTokensinto the rightSETTING_DEFSmulti-scope set + reactivate. (Per-plugin credential scoping mirrors the api-key path.)baseURL).Test levels (summary)
node --import tsx --test test/llmProviderOAuth*.test.ts— 19/19.