Skip to content

feat: add ChatGPT Pro OAuth as LLM provider - #476

Merged
shivammittal274 merged 17 commits into
mainfrom
feat/chatgpt-pro-oauth
Mar 18, 2026
Merged

feat: add ChatGPT Pro OAuth as LLM provider#476
shivammittal274 merged 17 commits into
mainfrom
feat/chatgpt-pro-oauth

Conversation

@shivammittal274

@shivammittal274 shivammittal274 commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

No description provided.

shivammittal274 and others added 2 commits March 18, 2026 01:52
Adds OAuth 2.0 (Authorization Code + PKCE) flow so users can authenticate
with their ChatGPT Pro subscription to power BrowserOS's agent, matching
the pattern used by Codex CLI, OpenCode, and Pi.

Server:
- OAuth token lifecycle (PKCE, exchange, refresh, SQLite storage)
- Dedicated callback server on port 1455 (Codex client ID registration)
- Codex fetch wrapper routing API calls to chatgpt.com/backend-api
- Config resolution + provider factories for all code paths (chat, test, refine)

Extension:
- ChatGPT Pro template card with OAuth flow trigger
- Status polling hook + auto-create provider on auth success
- Model list with Codex-supported models (gpt-5.x-codex family)
@greptile-apps

greptile-apps Bot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds ChatGPT Pro (OpenAI Codex) as an OAuth-based LLM provider, wiring together a full PKCE authorization flow: a new local Bun callback server on port 1455, SQLite-backed token storage with mutex-protected refresh, a custom codex-fetch wrapper that rewrites requests to the Codex endpoint, and a polling-based React hook that auto-creates the provider once authentication completes.

Key concerns:

  • Third-party OAuth client IDproviders.ts registers 'app_EMoamEEZ73f0CkXaXp7hrann', which is the Codex CLI's registered client ID, not a BrowserOS-owned application. OpenAI can revoke this at any time and shared usage may violate their developer ToS.
  • Dead shared constantsTIMEOUTS.OAUTH_POLL_INTERVAL and TIMEOUTS.OAUTH_POLL_TIMEOUT were added to the shared constants package but useOAuthStatus.ts hardcodes the equivalent values (2_000 / 300_000) instead of importing them.
  • Schema vs. code inconsistency — The oauth_tokens table declares refresh_token TEXT NOT NULL, but the code stores an empty string when no refresh token is returned. Allowing NULL would be semantically cleaner.
  • Duplicated model-creation logiccreateChatGPTProFactory in provider-factory.ts and createChatGPTProModel in llm/provider.ts are identical; a shared helper would reduce the maintenance surface.
  • The callback server's stop handle is still not returned from initializeOAuth or wired into the server's onShutdown callback (noted in a prior review thread).

Confidence Score: 2/5

  • Not safe to merge as-is due to the use of a third-party OAuth client ID that could be revoked by OpenAI without notice, potentially breaking the integration entirely.
  • The core OAuth flow (PKCE, state validation, mutex-protected refresh, SQLite persistence) is well-implemented. However, reusing the Codex CLI's registered client ID is a significant risk — it depends on infrastructure BrowserOS does not control and may violate OpenAI's developer terms. The dead constants and schema inconsistency are lower-severity but still indicate incomplete polish. The callback server shutdown issue from the prior thread also remains unaddressed.
  • packages/browseros-agent/apps/server/src/lib/clients/oauth/providers.ts (third-party client ID), packages/browseros-agent/packages/shared/src/constants/timeouts.ts (dead constants), packages/browseros-agent/apps/server/src/lib/db/schema.ts (NOT NULL constraint inconsistency)

Important Files Changed

Filename Overview
packages/browseros-agent/apps/server/src/lib/clients/oauth/providers.ts Defines OAuth provider config for ChatGPT Pro — hardcodes the Codex CLI's third-party client ID, which could violate OpenAI's ToS and break without warning if that ID is revoked.
packages/browseros-agent/packages/shared/src/constants/timeouts.ts Adds OAuth timeout constants; OAUTH_POLL_INTERVAL and OAUTH_POLL_TIMEOUT are defined here but never imported — useOAuthStatus.ts hardcodes equivalent values instead.
packages/browseros-agent/apps/server/src/lib/clients/oauth/token-manager.ts Well-implemented PKCE + state flow with mutex-protected refresh, cleanup of expired pending flows, and proper handling of missing refresh tokens.
packages/browseros-agent/apps/server/src/lib/clients/oauth/token-store.ts SQLite storage for tokens — refresh_token is declared NOT NULL but the code stores empty strings for absent tokens; the constraint should be nullable to reflect real semantics.
packages/browseros-agent/apps/server/src/lib/db/schema.ts Adds oauth_tokens table; refresh_token TEXT NOT NULL is semantically inconsistent with the empty-string fallback used when no refresh token is returned.
packages/browseros-agent/apps/server/src/lib/clients/oauth/codex-fetch.ts Custom fetch wrapper that rewrites requests to the Codex API endpoint, injects required headers and body fields; gracefully handles parse failures.
packages/browseros-agent/apps/server/src/lib/clients/oauth/index.ts Module entry — still discards the callback server's stop handle, preventing clean shutdown (flagged in prior review thread).
packages/browseros-agent/apps/agent/lib/llm-providers/useOAuthStatus.ts New polling hook for OAuth status — correctly cleans up on unmount and stops polling on auth success; hardcodes poll interval/timeout instead of using the shared TIMEOUTS constants.
packages/browseros-agent/apps/agent/entrypoints/app/ai-settings/AISettingsPage.tsx Integrates ChatGPT Pro OAuth flow into the AI settings UI — uses a ref guard to avoid spurious auto-creation on page load, tracks analytics events, and disconnects tokens on provider deletion.
packages/browseros-agent/apps/server/src/agent/provider-factory.ts Adds ChatGPT Pro factory for the ToolLoopAgent — logic is identical to createChatGPTProModel in llm/provider.ts, creating a maintenance duplication risk.

Sequence Diagram

sequenceDiagram
    participant UI as AISettingsPage (Extension)
    participant AgentServer as Agent Server (Hono)
    participant CallbackServer as OAuth Callback Server (port 1455)
    participant OpenAI as OpenAI Auth (auth.openai.com)
    participant TokenStore as SQLite (oauth_tokens)

    UI->>AgentServer: GET /oauth/chatgpt-pro/start?redirect=...
    AgentServer->>AgentServer: Generate PKCE verifier + challenge + state
    AgentServer->>AgentServer: Store PendingOAuthFlow in memory
    AgentServer-->>UI: 302 Redirect to OpenAI auth URL

    UI->>OpenAI: User authenticates in new tab
    OpenAI-->>CallbackServer: GET /auth/callback?code=...&state=...
    CallbackServer->>AgentServer: tokenManager.handleCallback(code, state)
    AgentServer->>OpenAI: POST /oauth/token (code + verifier)
    OpenAI-->>AgentServer: access_token + refresh_token
    AgentServer->>AgentServer: parseAccessTokenClaims (accountId, email)
    AgentServer->>TokenStore: upsertTokens(browseros_id, chatgpt-pro, tokens)
    AgentServer-->>CallbackServer: StoredOAuthTokens
    CallbackServer-->>UI: HTML success page

    UI->>AgentServer: GET /oauth/chatgpt-pro/status (polling every 2s)
    AgentServer->>TokenStore: getStatus(browseros_id, chatgpt-pro)
    TokenStore-->>AgentServer: {authenticated: true, email}
    AgentServer-->>UI: {authenticated: true, email}
    UI->>UI: saveProvider({type: chatgpt-pro, ...})

    Note over UI,TokenStore: On LLM request
    UI->>AgentServer: POST /chat (provider: chatgpt-pro)
    AgentServer->>TokenStore: refreshIfExpired(chatgpt-pro)
    TokenStore-->>AgentServer: valid access_token
    AgentServer->>OpenAI: POST chatgpt.com/backend-api/codex/responses
    OpenAI-->>AgentServer: streamed response
    AgentServer-->>UI: streamed response
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: packages/browseros-agent/packages/shared/src/constants/timeouts.ts
Line: 1686-1687

Comment:
**Dead constants never consumed**

`OAUTH_POLL_INTERVAL` and `OAUTH_POLL_TIMEOUT` were added here, but `useOAuthStatus.ts` hardcodes the same values (`2_000` and `300_000`) directly instead of importing these constants. The shared values are effectively dead code.

```suggestion
  OAUTH_POLL_INTERVAL: 2_000,
  OAUTH_POLL_TIMEOUT: 300_000,
```

These constants should be imported and used in `useOAuthStatus.ts`:
```ts
import { TIMEOUTS } from '@browseros/shared/constants/timeouts'
// ...
}, TIMEOUTS.OAUTH_POLL_INTERVAL)
pollTimeoutRef.current = setTimeout(stopPolling, TIMEOUTS.OAUTH_POLL_TIMEOUT)
```
Otherwise remove them from `timeouts.ts` to avoid confusion.

**Rule Used:** Remove unused/dead code rather than leaving it in ... ([source](https://app.greptile.com/review/custom-context?memory=9b045db4-2630-428c-95b7-ccf048d34547))

**Learnt From**
[browseros-ai/BrowserOS-agent#126](https://github.com/browseros-ai/BrowserOS-agent/pull/126)

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: packages/browseros-agent/apps/server/src/lib/clients/oauth/providers.ts
Line: 1227

Comment:
**Reusing a third-party client ID**

`'app_EMoamEEZ73f0CkXaXp7hrann'` is the client ID registered by the Codex CLI, not by BrowserOS. The `callback-server.ts` comment even acknowledges this: _"matching the Codex CLI client ID registration."_

Relying on another application's OAuth registration creates real fragility:
- OpenAI can revoke or rotate this client ID at any time, breaking BrowserOS's entire ChatGPT Pro integration with no warning.
- It may violate OpenAI's developer Terms of Service, which typically prohibit impersonating another registered application.
- Rate limits and usage quotas applied to the Codex CLI will be shared with all BrowserOS users.

BrowserOS should register its own OAuth application with OpenAI and use its own client ID here.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: packages/browseros-agent/apps/server/src/lib/db/schema.ts
Line: 1648

Comment:
**`NOT NULL` constraint inconsistent with empty-string fallback**

The column is declared `NOT NULL`, but `token-manager.ts` stores `data.refresh_token ?? ''` when the authorization server omits `refresh_token`. This means the "absent" state is represented by an empty string rather than `NULL`, which makes the constraint semantically misleading and could silently obscure missing tokens in direct DB queries.

The `executeRefresh` method correctly handles the empty-string case (deletes the row and throws), but the schema itself should reflect the real semantics:

```suggestion
  refresh_token TEXT,
```

And update `StoredOAuthTokens.refreshToken` to `refreshToken?: string` accordingly. The `executeRefresh` guard (`if (!tokens.refreshToken)`) already handles both `''` and `undefined`/`null` correctly, so no other logic changes would be needed.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: packages/browseros-agent/apps/server/src/agent/provider-factory.ts
Line: 574-583

Comment:
**Duplicated ChatGPT Pro model-creation logic**

`createChatGPTProFactory` here and `createChatGPTProModel` in `lib/clients/llm/provider.ts` (lines 879–886) are byte-for-byte identical in logic: both check `config.apiKey`, call `createOpenAI({ apiKey, fetch: createCodexFetch(config.accountId) }).responses`, and throw the same error message. If the Codex endpoint URL or auth flow changes, both files must be updated in sync.

Consider extracting a shared `buildChatGPTProClient(apiKey, accountId)` helper into `oauth/codex-fetch.ts` or a new `chatgpt-pro.ts` utility, then calling it from both factories.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "fix: address Greptil..."

Comment thread packages/browseros-agent/apps/server/src/lib/clients/oauth/index.ts
- Wire OAuth callback server stop handle into onShutdown (P1: port 1455 leak)
- Guard against missing refresh token + clear stale tokens on failed refresh (P1)
- Add logger.warn to silent catch in codex-fetch body mutation
- Document JWT trust assumption in parseAccessTokenClaims
- Source model ID from provider template instead of hard-coding
- Revert OAuthHandle interface — callback server port releases on process exit
- Remove stopCallbackServer from shutdown flow (dead code)
- Remove all useCallback from useOAuthStatus per CLAUDE.md guidance
@shivammittal274

Copy link
Copy Markdown
Contributor Author

@greptile-ai review

- Pass providerOptions { openai: { store: false } } to ToolLoopAgent
  so the AI SDK inlines content instead of using item_reference
- Strip item IDs and previous_response_id in codex-fetch (safety net)
- Use .responses() model (Codex only speaks Responses API format)
- Strip temperature, max_tokens, top_p from Codex requests (unsupported)
- Add all available Codex models including gpt-5.4, gpt-5.2, gpt-5.1
- Add reasoningEffort (none/low/medium/high) and reasoningSummary
  (auto/concise/detailed) dropdowns in the Edit Provider dialog
- Pass through extension → chat request → agent config → providerOptions
- Defaults: effort=high, summary=auto
@shivammittal274

Copy link
Copy Markdown
Contributor Author

@greptile-ai review

- Fix default model fallback: gpt-4o → gpt-5.3-codex (Codex endpoint)
- Clear stale tokens on refresh failure (prevents infinite retry loop)
- Only auto-create provider after explicit OAuth flow, not on page load
- Add catch block to auto-create effect with error toast
@shivammittal274

Copy link
Copy Markdown
Contributor Author

@greptile-ai review

@shivammittal274
shivammittal274 merged commit 46a8326 into main Mar 18, 2026
4 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant