Repository navigation
feat(local): use a ChatGPT Plus or Pro plan as a chat model - #2151
Merged
AnishSarkar22 merged 4 commits intoOct 2, 2026
Merged
Conversation
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.
|
@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. |
|
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 configurationConfiguration used: Repository: MODSetter/SurfSense/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (87)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
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.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.auth_kind,oauth_ciphertextandtoken_versiontoprovider_connectionsin place, without a table rebuild, so existing connections and their selected models are untouched.connections/serves.py), sent to the app asserves. 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".servesin each settings section and the dialog, and the Electron link allowlist.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_genonly.⏱️ Estimated Review Time: 1-3 hours
💡 Review Order Suggestion
docs/adr/0038-chatgpt-plans-sign-in-through-openai-not-codex.mddocs/architecture/chatgpt-subscription.mddocs/adr/0015-openai-compatible-connections.mddocs/adr/README.mdsurfsense_local/backend/alembic/versions/0023_connection_sign_in.pysurfsense_local/backend/modules/llm/models.pysurfsense_local/backend/modules/llm/subscriptions/chatgpt/endpoints.pysurfsense_local/backend/modules/llm/subscriptions/chatgpt/pkce.pysurfsense_local/backend/modules/llm/subscriptions/chatgpt/host_id.pysurfsense_local/backend/modules/llm/subscriptions/chatgpt/token_set.pysurfsense_local/backend/modules/llm/subscriptions/chatgpt/id_token.pysurfsense_local/backend/modules/llm/subscriptions/chatgpt/loopback.pysurfsense_local/backend/modules/llm/subscriptions/chatgpt/token_endpoint.pysurfsense_local/backend/modules/llm/subscriptions/chatgpt/account.pysurfsense_local/backend/modules/llm/subscriptions/chatgpt/tokens.pysurfsense_local/backend/modules/llm/subscriptions/chatgpt/flows.pysurfsense_local/backend/modules/llm/providers/openai_responses/errors.pysurfsense_local/backend/modules/llm/providers/openai_responses/refusal.pysurfsense_local/backend/modules/llm/providers/openai_responses/input_items.pysurfsense_local/backend/modules/llm/providers/openai_responses/events.pysurfsense_local/backend/modules/llm/providers/openai_responses/chat.pysurfsense_local/backend/modules/llm/providers/openai_responses/__init__.pysurfsense_local/backend/modules/llm/connections/serves.pysurfsense_local/backend/modules/llm/connections/listing.pysurfsense_local/backend/modules/llm/subscriptions/chatgpt/plan_models.pysurfsense_local/backend/modules/llm/subscriptions/chatgpt/router.pysurfsense_local/backend/modules/llm/connections/router.pysurfsense_local/backend/modules/llm/router.pysurfsense_local/backend/modules/llm/schemas.pysurfsense_local/backend/modules/llm/resolution.pysurfsense_local/backend/modules/llm/selection.pysurfsense_local/backend/modules/chat/errors.pysurfsense_local/backend/shared/secrets.pydocs/architecture/chat.mddocs/architecture/connections.mddocs/architecture/data-model.mddocs/architecture/overview.mdsurfsense_local/backend/tests/integration/llm/chatgpt/fake_openai.pysurfsense_local/backend/tests/integration/llm/chatgpt/conftest.pysurfsense_local/backend/tests/integration/llm/chatgpt/test_sign_in_routes.pysurfsense_local/backend/tests/integration/llm/chatgpt/test_token_refresh.pysurfsense_local/backend/tests/unit/llm/providers/test_openai_responses.pysurfsense_local/backend/tests/unit/chat/test_errors.pysurfsense_local/backend/tests/integration/test_migration_0023.pysurfsense_local/backend/tests/integration/llm/test_connections.pysurfsense_local/frontend/src/features/models/remote/connections/chatgpt/api.tssurfsense_local/frontend/src/features/models/remote/connections/chatgpt/use-sign-in.tssurfsense_local/frontend/src/features/models/remote/connections/chatgpt/chatgpt-sign-in.tsxsurfsense_local/frontend/src/features/models/remote/connections/connection-form.tsxsurfsense_local/frontend/src/features/models/remote/connections/api.tssurfsense_local/frontend/src/features/models/remote/connections/connection-dialog.tsxsurfsense_local/frontend/src/features/models/remote/connections/use-connections.tssurfsense_local/frontend/src/features/models/remote/models/server-model-picker.tsxsurfsense_local/frontend/src/features/models/remote/models/server-models.tsxsurfsense_local/frontend/src/features/models/add-model/add-model-options.tsxsurfsense_local/frontend/src/features/models/add-model/server-card.tsxsurfsense_local/frontend/src/features/onboarding/model-step/model-step.tsxsurfsense_local/frontend/src/features/onboarding/model-step/server/server-path.tsxsurfsense_local/frontend/src/features/settings/models/model-slot-settings.tsxsurfsense_local/frontend/src/features/chat/chat-error-text.tssurfsense_local/frontend/src/features/chat/chat-error-notice.tsxsurfsense_local/frontend/src/features/chat/model-issue-notice.tsxsurfsense_local/frontend/src/features/chat/sse.tssurfsense_local/electron/src/main/external-url.tssurfsense_local/electron/src/main/external-url.test.tssurfsense_local/frontend/src/features/models/remote/connections/chatgpt/chatgpt-sign-in.test.tsxsurfsense_local/frontend/src/features/models/remote/connections/connection-form.test.tsxsurfsense_local/frontend/src/features/settings/models/video-models-settings.test.tsxsurfsense_local/frontend/src/features/settings/models/chat-models-settings.test.tsxsurfsense_local/frontend/src/features/models/remote/models/server-model-picker.test.tsxsurfsense_local/frontend/src/features/chat/model-picker.test.tsxsurfsense_local/frontend/src/features/onboarding/onboarding-page.test.tsxsurfsense_local/frontend/src/features/settings/settings-dialog.test.tsx.agents/skills/translate/glossary.mdsurfsense_local/frontend/translations/en.jsonsurfsense_local/frontend/translations/ja.jsonsurfsense_local/frontend/translations/de.jsonsurfsense_local/frontend/translations/es.jsonsurfsense_local/frontend/translations/fr.jsonsurfsense_local/frontend/translations/hi.jsonsurfsense_local/frontend/translations/ko.jsonsurfsense_local/frontend/translations/pt-BR.jsonsurfsense_local/frontend/translations/ru.jsonsurfsense_local/frontend/translations/zh-CN.json.agents/skills/translate/glossary.mdSummary by CodeRabbit