Skip to content

feat(local): use a ChatGPT Plus or Pro plan as a chat model - #2151

Merged
AnishSarkar22 merged 4 commits into
MODSetter:devfrom
AnishSarkar22:feat/chatgpt-subscription
Oct 2, 2026
Merged

AnishSarkar22 merged 4 commits into
MODSetter:devfrom
AnishSarkar22:feat/chatgpt-subscription

Conversation

@AnishSarkar22

@AnishSarkar22 AnishSarkar22 commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

What

  • Adds "ChatGPT subscription" to the connect-a-server dialog: the user signs in with their ChatGPT account in the browser instead of pasting an API key, then picks a model their Plus or Pro plan lists for chat.
  • Sign-in follows OpenAI's documented "Sign in with ChatGPT" flow for open-source apps: OpenAI issues a client per user on first sign-in, the code returns to a one-time 127.0.0.1 callback, and the ID token is verified against OpenAI's published keys. The Codex CLI's client ID is not used; ADR 0038 records why.
  • Chat, thread titles and Studio reach the plan through a new Responses API generator (POST api.openai.com/v1/responses). It sends only the fields the plan accepts, so per-request token limits, reasoning settings and JSON schemas are dropped for these models.
  • Tokens are encrypted with the install secret in surfsense.db. Refreshes hold SQLite's write lock, so the API and both workers never use the same rotating refresh token twice and sign the account out.
  • Migration 0023 adds auth_kind, oauth_ciphertext and token_version to provider_connections in place, without a table rebuild, so existing connections and their selected models are untouched.
  • Which model types a connection can serve is one backend rule (connections/serves.py), sent to the app as serves. A ChatGPT plan serves chat only, so it no longer appears in the image, image editing, video or audio sections, and the dialog offers it only from chat, marked "Chat only".
  • Chat shows its own messages for a ChatGPT account that has to sign in again and for a reached plan usage limit, instead of "needs a new API key" or "try again".
  • Electron opens OpenAI's sign-in page only when it redirects back to 127.0.0.1, so a crafted link can't send the sign-in code elsewhere.
  • New strings are translated into all nine languages, and the glossary gains "sign in", "plan" and "subscription".
  • Docs: a new architecture page, updated connections, data model and chat pages, ADR 0038, and ADR 0015's status marking the part 0038 supersedes. The proposal is deleted, per the docs process.
  • Tests: migration 0023, the Responses generator, token refresh (including two processes refreshing at once), the sign-in routes against a fake OpenAI server, serves in each settings section and the dialog, and the Electron link allowlist.
  • Not yet tested against a real ChatGPT account, only the fake; this is listed under Known gaps.
  • To test: open Settings, then Chat, then Add model, then Connect. Choose ChatGPT subscription and Sign in with ChatGPT, approve in the browser, pick a model, and send a message. Then open Image settings and confirm the ChatGPT connection is not listed.

Why

Fixes #

How to test

High-level PR Summary

This PR adds ChatGPT subscription support, allowing users to sign in with their ChatGPT Plus or Pro accounts and use their plan's models for chat without an API key. The implementation uses OpenAI's OAuth flow with PKCE, stores encrypted tokens in SQLite, and handles token refresh to prevent sign-out from token rotation. The feature includes a dedicated sign-in dialog, connection management, and integration with the existing model selection system, restricted to text_gen only.

⏱️ Estimated Review Time: 1-3 hours

💡 Review Order Suggestion
Order File Path
1 docs/adr/0038-chatgpt-plans-sign-in-through-openai-not-codex.md
2 docs/architecture/chatgpt-subscription.md
3 docs/adr/0015-openai-compatible-connections.md
4 docs/adr/README.md
5 surfsense_local/backend/alembic/versions/0023_connection_sign_in.py
6 surfsense_local/backend/modules/llm/models.py
7 surfsense_local/backend/modules/llm/subscriptions/chatgpt/endpoints.py
8 surfsense_local/backend/modules/llm/subscriptions/chatgpt/pkce.py
9 surfsense_local/backend/modules/llm/subscriptions/chatgpt/host_id.py
10 surfsense_local/backend/modules/llm/subscriptions/chatgpt/token_set.py
11 surfsense_local/backend/modules/llm/subscriptions/chatgpt/id_token.py
12 surfsense_local/backend/modules/llm/subscriptions/chatgpt/loopback.py
13 surfsense_local/backend/modules/llm/subscriptions/chatgpt/token_endpoint.py
14 surfsense_local/backend/modules/llm/subscriptions/chatgpt/account.py
15 surfsense_local/backend/modules/llm/subscriptions/chatgpt/tokens.py
16 surfsense_local/backend/modules/llm/subscriptions/chatgpt/flows.py
17 surfsense_local/backend/modules/llm/providers/openai_responses/errors.py
18 surfsense_local/backend/modules/llm/providers/openai_responses/refusal.py
19 surfsense_local/backend/modules/llm/providers/openai_responses/input_items.py
20 surfsense_local/backend/modules/llm/providers/openai_responses/events.py
21 surfsense_local/backend/modules/llm/providers/openai_responses/chat.py
22 surfsense_local/backend/modules/llm/providers/openai_responses/__init__.py
23 surfsense_local/backend/modules/llm/connections/serves.py
24 surfsense_local/backend/modules/llm/connections/listing.py
25 surfsense_local/backend/modules/llm/subscriptions/chatgpt/plan_models.py
26 surfsense_local/backend/modules/llm/subscriptions/chatgpt/router.py
27 surfsense_local/backend/modules/llm/connections/router.py
28 surfsense_local/backend/modules/llm/router.py
29 surfsense_local/backend/modules/llm/schemas.py
30 surfsense_local/backend/modules/llm/resolution.py
31 surfsense_local/backend/modules/llm/selection.py
32 surfsense_local/backend/modules/chat/errors.py
33 surfsense_local/backend/shared/secrets.py
34 docs/architecture/chat.md
35 docs/architecture/connections.md
36 docs/architecture/data-model.md
37 docs/architecture/overview.md
38 surfsense_local/backend/tests/integration/llm/chatgpt/fake_openai.py
39 surfsense_local/backend/tests/integration/llm/chatgpt/conftest.py
40 surfsense_local/backend/tests/integration/llm/chatgpt/test_sign_in_routes.py
41 surfsense_local/backend/tests/integration/llm/chatgpt/test_token_refresh.py
42 surfsense_local/backend/tests/unit/llm/providers/test_openai_responses.py
43 surfsense_local/backend/tests/unit/chat/test_errors.py
44 surfsense_local/backend/tests/integration/test_migration_0023.py
45 surfsense_local/backend/tests/integration/llm/test_connections.py
46 surfsense_local/frontend/src/features/models/remote/connections/chatgpt/api.ts
47 surfsense_local/frontend/src/features/models/remote/connections/chatgpt/use-sign-in.ts
48 surfsense_local/frontend/src/features/models/remote/connections/chatgpt/chatgpt-sign-in.tsx
49 surfsense_local/frontend/src/features/models/remote/connections/connection-form.tsx
50 surfsense_local/frontend/src/features/models/remote/connections/api.ts
51 surfsense_local/frontend/src/features/models/remote/connections/connection-dialog.tsx
52 surfsense_local/frontend/src/features/models/remote/connections/use-connections.ts
53 surfsense_local/frontend/src/features/models/remote/models/server-model-picker.tsx
54 surfsense_local/frontend/src/features/models/remote/models/server-models.tsx
55 surfsense_local/frontend/src/features/models/add-model/add-model-options.tsx
56 surfsense_local/frontend/src/features/models/add-model/server-card.tsx
57 surfsense_local/frontend/src/features/onboarding/model-step/model-step.tsx
58 surfsense_local/frontend/src/features/onboarding/model-step/server/server-path.tsx
59 surfsense_local/frontend/src/features/settings/models/model-slot-settings.tsx
60 surfsense_local/frontend/src/features/chat/chat-error-text.ts
61 surfsense_local/frontend/src/features/chat/chat-error-notice.tsx
62 surfsense_local/frontend/src/features/chat/model-issue-notice.tsx
63 surfsense_local/frontend/src/features/chat/sse.ts
64 surfsense_local/electron/src/main/external-url.ts
65 surfsense_local/electron/src/main/external-url.test.ts
66 surfsense_local/frontend/src/features/models/remote/connections/chatgpt/chatgpt-sign-in.test.tsx
67 surfsense_local/frontend/src/features/models/remote/connections/connection-form.test.tsx
68 surfsense_local/frontend/src/features/settings/models/video-models-settings.test.tsx
69 surfsense_local/frontend/src/features/settings/models/chat-models-settings.test.tsx
70 surfsense_local/frontend/src/features/models/remote/models/server-model-picker.test.tsx
71 surfsense_local/frontend/src/features/chat/model-picker.test.tsx
72 surfsense_local/frontend/src/features/onboarding/onboarding-page.test.tsx
73 surfsense_local/frontend/src/features/settings/settings-dialog.test.tsx
74 .agents/skills/translate/glossary.md
75 surfsense_local/frontend/translations/en.json
76 surfsense_local/frontend/translations/ja.json
77 surfsense_local/frontend/translations/de.json
78 surfsense_local/frontend/translations/es.json
79 surfsense_local/frontend/translations/fr.json
80 surfsense_local/frontend/translations/hi.json
81 surfsense_local/frontend/translations/ko.json
82 surfsense_local/frontend/translations/pt-BR.json
83 surfsense_local/frontend/translations/ru.json
84 surfsense_local/frontend/translations/zh-CN.json
⚠️ Inconsistent Changes Detected
File Path Warning
.agents/skills/translate/glossary.md A translation glossary update appears unrelated to implementing ChatGPT subscription functionality. While the new terms (sign in, plan, subscription) are relevant to the feature, this seems like a preparatory or documentation-only change rather than core implementation.

Need help? Join our Discord

Summary by CodeRabbit

  • New Features
    • Connect a ChatGPT plan by signing in through your browser, then select its models for text generation.
    • View the signed-in account and manage ChatGPT connections, including renaming, signing out, and signing in again.
    • Model setup now shows connections only for the model types they support.
  • Bug Fixes
    • Chat errors now explain when ChatGPT sign-in is needed or a plan usage limit has been reached.
  • Documentation
    • Added guidance on ChatGPT plan connections, supported capabilities, and sign-in behavior.

A ChatGPT Plus or Pro account can now be a connection: the user signs in in the browser instead of pasting a key, and the plan's models answer chat through the Responses API.

- Sign-in follows OpenAI's flow for open-source apps: a dynamically issued client, PKCE, a loopback callback on any port, and an ID token checked against OpenAI's JWKS.
- Revision 0023 adds auth_kind, oauth_ciphertext and token_version to provider_connections in place, so no selection cascades away.
- Tokens are encrypted with the install secret; the SQLite write lock makes one process refresh at a time, so a rotated refresh token is never sent
  twice.
- A Responses generator sends only what the plan allows and fails on a plan limit or a revoked sign-in with their own chat error kinds.
- Electron opens OpenAI's authorize page only when it redirects to loopback.
- The connection form gains a ChatGPT subscription option, translated.
A ChatGPT connection showed up as an empty group in the image, image edit, video and audio sections, and the rule that it only answers chat was repeated in selection, resolution and the model tests.

- connections/serves.py is the one rule, keyed by auth_kind: a key connection serves every slot, a ChatGPT one text_gen alone.
- Selection, resolution, the image and speech tests and the plan's model list all read it, and GET /llm/connections reports it as `serves`.
- GET /llm/connections/chatgpt replaces /chatgpt/hosts and returns the sign-in's `serves` with the hosts it still needs.
- The frontend only reads `serves`: the Settings sections, their empty states and onboarding's server steps list only connections that fill their slot, and the connect dialog offers ChatGPT only where it can serve, hinted "Chat only".
…osal

ADR 0038 records why a ChatGPT plan connects through OpenAI's sign-in for open-source apps rather than the Codex CLI's client, how its tokens are kept and refreshed, and that one rule decides which slots a connection serves.

- ADR 0015's Status line names the part 0038 supersedes: a connection
  that only takes a bearer key and only chats on /chat/completions.
- The ChatGPT architecture page links its ADRs instead of the proposal.
- The proposal is deleted; 0038 cites it by permalink.
@vercel

vercel Bot commented Oct 2, 2026

Copy link
Copy Markdown

@AnishSarkar22 is attempting to deploy a commit to the Rohan Verma's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Oct 2, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Repository: MODSetter/SurfSense/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 5869edcf-5346-4eca-86b4-d9318fc1b9e7

📥 Commits

Reviewing files that changed from the base of the PR and between 7ca2cb9 and 69d4ab6.

📒 Files selected for processing (87)
  • .agents/skills/translate/glossary.md
  • docs/adr/0015-openai-compatible-connections.md
  • docs/adr/0038-chatgpt-plans-sign-in-through-openai-not-codex.md
  • docs/adr/README.md
  • docs/architecture/chat.md
  • docs/architecture/chatgpt-subscription.md
  • docs/architecture/connections.md
  • docs/architecture/data-model.md
  • docs/architecture/overview.md
  • surfsense_local/backend/alembic/versions/0023_connection_sign_in.py
  • surfsense_local/backend/modules/chat/errors.py
  • surfsense_local/backend/modules/llm/connections/listing.py
  • surfsense_local/backend/modules/llm/connections/router.py
  • surfsense_local/backend/modules/llm/connections/serves.py
  • surfsense_local/backend/modules/llm/models.py
  • surfsense_local/backend/modules/llm/providers/openai_responses/__init__.py
  • surfsense_local/backend/modules/llm/providers/openai_responses/chat.py
  • surfsense_local/backend/modules/llm/providers/openai_responses/errors.py
  • surfsense_local/backend/modules/llm/providers/openai_responses/events.py
  • surfsense_local/backend/modules/llm/providers/openai_responses/input_items.py
  • surfsense_local/backend/modules/llm/providers/openai_responses/refusal.py
  • surfsense_local/backend/modules/llm/resolution.py
  • surfsense_local/backend/modules/llm/router.py
  • surfsense_local/backend/modules/llm/schemas.py
  • surfsense_local/backend/modules/llm/selection.py
  • surfsense_local/backend/modules/llm/subscriptions/__init__.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/__init__.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/account.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/endpoints.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/flows.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/host_id.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/id_token.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/loopback.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/pkce.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/plan_models.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/router.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/schemas.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/token_endpoint.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/token_set.py
  • surfsense_local/backend/modules/llm/subscriptions/chatgpt/tokens.py
  • surfsense_local/backend/shared/secrets.py
  • surfsense_local/backend/tests/integration/llm/chatgpt/conftest.py
  • surfsense_local/backend/tests/integration/llm/chatgpt/fake_openai.py
  • surfsense_local/backend/tests/integration/llm/chatgpt/test_sign_in_routes.py
  • surfsense_local/backend/tests/integration/llm/chatgpt/test_token_refresh.py
  • surfsense_local/backend/tests/integration/llm/test_connections.py
  • surfsense_local/backend/tests/integration/test_migration_0023.py
  • surfsense_local/backend/tests/unit/chat/test_errors.py
  • surfsense_local/backend/tests/unit/llm/providers/test_openai_responses.py
  • surfsense_local/electron/src/main/external-url.test.ts
  • surfsense_local/electron/src/main/external-url.ts
  • surfsense_local/frontend/src/features/chat/chat-error-notice.tsx
  • surfsense_local/frontend/src/features/chat/chat-error-text.ts
  • surfsense_local/frontend/src/features/chat/model-issue-notice.tsx
  • surfsense_local/frontend/src/features/chat/model-picker.test.tsx
  • surfsense_local/frontend/src/features/chat/sse.ts
  • surfsense_local/frontend/src/features/models/add-model/add-model-options.tsx
  • surfsense_local/frontend/src/features/models/add-model/server-card.tsx
  • surfsense_local/frontend/src/features/models/remote/connections/api.ts
  • surfsense_local/frontend/src/features/models/remote/connections/chatgpt/api.ts
  • surfsense_local/frontend/src/features/models/remote/connections/chatgpt/chatgpt-sign-in.test.tsx
  • surfsense_local/frontend/src/features/models/remote/connections/chatgpt/chatgpt-sign-in.tsx
  • surfsense_local/frontend/src/features/models/remote/connections/chatgpt/use-sign-in.ts
  • surfsense_local/frontend/src/features/models/remote/connections/connection-dialog.tsx
  • surfsense_local/frontend/src/features/models/remote/connections/connection-form.test.tsx
  • surfsense_local/frontend/src/features/models/remote/connections/connection-form.tsx
  • surfsense_local/frontend/src/features/models/remote/connections/use-connections.ts
  • surfsense_local/frontend/src/features/models/remote/models/server-model-picker.test.tsx
  • surfsense_local/frontend/src/features/models/remote/models/server-model-picker.tsx
  • surfsense_local/frontend/src/features/models/remote/models/server-models.tsx
  • surfsense_local/frontend/src/features/onboarding/model-step/model-step.tsx
  • surfsense_local/frontend/src/features/onboarding/model-step/server/server-path.tsx
  • surfsense_local/frontend/src/features/onboarding/onboarding-page.test.tsx
  • surfsense_local/frontend/src/features/settings/models/chat-models-settings.test.tsx
  • surfsense_local/frontend/src/features/settings/models/model-slot-settings.tsx
  • surfsense_local/frontend/src/features/settings/models/video-models-settings.test.tsx
  • surfsense_local/frontend/src/features/settings/settings-dialog.test.tsx
  • surfsense_local/frontend/translations/de.json
  • surfsense_local/frontend/translations/en.json
  • surfsense_local/frontend/translations/es.json
  • surfsense_local/frontend/translations/fr.json
  • surfsense_local/frontend/translations/hi.json
  • surfsense_local/frontend/translations/ja.json
  • surfsense_local/frontend/translations/ko.json
  • surfsense_local/frontend/translations/pt-BR.json
  • surfsense_local/frontend/translations/ru.json
  • surfsense_local/frontend/translations/zh-CN.json
 _______________________________
< BOFH: Bunny of Friendly Help. >
 -------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@AnishSarkar22 AnishSarkar22 changed the title Feat/chatgpt subscription feat(local): use a ChatGPT Plus or Pro plan as a chat model Oct 2, 2026
@AnishSarkar22
AnishSarkar22 merged commit e6088b2 into MODSetter:dev Oct 2, 2026
21 of 24 checks passed
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