Repository navigation
feat: chatgpt_plan component — users fund LLM calls with their own ChatGPT plan (draft, flag off) - #118
Draft
calmmage wants to merge 1 commit into
Draft
feat: chatgpt_plan component — users fund LLM calls with their own ChatGPT plan (draft, flag off)#118calmmage wants to merge 1 commit into
calmmage wants to merge 1 commit into
Conversation
New botspot/components/new/chatgpt_plan: users link their own ChatGPT plan via OAuth 2 + PKCE and LLM calls run on the Responses API with their token. - settings (BOTSPOT_CHATGPT_PLAN_*, enabled=false), registered in BotspotSettings, DependencyManager, BotManager and deps getters - oauth: PKCE S256, single-use state + nonce in a TTL collection, code exchange, scope check, id_token claim checks; JWKS signature verification is a TODO hook - store: Fernet-encrypted tokens, refresh within margin (rolling), needs_relink/capped - client: respond (store=false, stream=true, success only on response.completed, disallowed params stripped, bounded 503 backoff, unsupported-param drop), list_models - errors: OpenAI error code / status mapping table - web: aiohttp OAuth callback route factory with an on_linked hook - cryptography added to the extras group (Fernet) Flag off: every call raises PlanDisabled with no I/O. Untested against OpenAI until a partner client ID is issued; all HTTP in tests is faked. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This branch has not been deployed
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.
Request
Petr, 01 Oct 2026 (verbatim):
Scope answer: "Design PRs now": a design doc plus testable scaffolding behind an off flag. Real OAuth gets wired once OpenAI issues a client ID. This is S1, the botspot half. S2 (whisper bot wiring) depends on it.
What's built
New package
botspot/components/new/chatgpt_plan/. It is off by default. WithBOTSPOT_CHATGPT_PLAN_ENABLED=false, every call raisesPlanDisabledbefore any I/O.settings.py:ChatgptPlanSettings(env prefixBOTSPOT_CHATGPT_PLAN_). It is registered inBotspotSettings,DependencyManager(deps.chatgpt_plan, Optional),BotManager(initialized only when enabled) andutils.deps_getters.get_chatgpt_plan.oauth.py: "Sign in with ChatGPT".state+nonce.chatgpt_oauth_pending, with a TTL index and single-usefind_one_and_delete.chatgpt.tokens.use.direct.iss,aud,nonce,sub.store.py: Mongochatgpt_links, keyed by Telegram user id.sub,email,client_id, Fernet-encrypted refresh and access tokens,expires_at,scopes,status(active/needs_relink/capped+capped_until),linked_at,last_used_at.needs_relink. Tokens are never logged.client.py: Responses API only.store=falseandstream=true, and accumulates the output text. A call succeeds only afterresponse.completed.temperature,top_p,max_output_tokens,metadata,user,previous_response_id,background, tools).unsupported_capabilityit dropserror.paramand retries.list_modelskeeps only models withvisibility == "list".errors.py: the exception classes and an error-code / HTTP-status mapping table:subscription_sharing_usage_limit_exceededPlanUsageCapped(reset_hint)cappeduntil the hint orCAP_PAUSE_SECONDSinvalid_grant/invalid_refresh_token/token_expiredPlanNeedsRelinkneeds_relinksubscription_sharing_usage_unavailablePlanUnavailablePlanUnavailable(retryable=False)subscription_sharing_unsupported_capabilityPlanUnsupported(param)web.py: an aiohttp handler factory forGET <redirect_uri path>. It callscomplete_linkand renders a tiny "Linked. Go back to Telegram" page. Anon_linked(result)hook lets the host bot message the user.README.mdin the package covers the blocker, config, error table and what is untested. There is also a demo inexamples/components_examples/chatgpt_plan_demo/and one line indocs/index.md.cryptography>=42is added to theextrasgroup, the same group that holdspymongo. Fernet needs it. It was already inuv.lockas a transitive package, so the lock only gains the direct reference. HTTP usesaiohttp, which botspot already uses.llm_provider.pyandsubscription_manager/are not touched.Untested until OpenAI issues a client ID
All HTTP in the tests is faked: a fake transport, plus one local aiohttp test server on loopback for the real transport. None of the following has ever talked to OpenAI:
resource(sent on authorize, code exchange and refresh) andagent_name_hintare accepted;scope,id_token, refresh rolling);oauth.verify_id_token_signature, which logs a warning.iss/aud/noncechecks.ChatgptPlan(..., id_token_verifier=...).unlinkonly deletes our stored tokens; it does not revoke them at OpenAI.Eligibility blocker
Self-serve SIWC client IDs are only for open-source, locally hosted apps, which may only use a loopback redirect (
http://127.0.0.1:{port}/callback). Paid or remotely hosted apps, which includes us, have to apply through the interest form: https://openai.com/form/sign-in-with-chatgpt-interest/ ("select group of commercial partners"). The client ID and the partner redirect rules come with approval. So the flag stays off, and every endpoint, scope and redirect is configurable. Petr has to submit the form himself.Config (env)
BOTSPOT_CHATGPT_PLAN_ENABLEDfalseBOTSPOT_CHATGPT_PLAN_CLIENT_IDBOTSPOT_CHATGPT_PLAN_REDIRECT_URIBOTSPOT_CHATGPT_PLAN_TOKEN_ENCRYPTION_KEYBOTSPOT_CHATGPT_PLAN_ISSUERhttps://auth.openai.comBOTSPOT_CHATGPT_PLAN_AUTHORIZE_PATH/api/accounts/authorizeBOTSPOT_CHATGPT_PLAN_TOKEN_PATH/api/accounts/oauth/tokenBOTSPOT_CHATGPT_PLAN_RESOURCEhttps://api.openai.com/v1BOTSPOT_CHATGPT_PLAN_API_BASEhttps://api.openai.com/v1BOTSPOT_CHATGPT_PLAN_SCOPESopenid profile email offline_access resource.invoke chatgpt.tokens.use.directBOTSPOT_CHATGPT_PLAN_AGENT_NAME_HINT""BOTSPOT_CHATGPT_PLAN_PENDING_TTL_SECONDS600BOTSPOT_CHATGPT_PLAN_REFRESH_MARGIN_SECONDS300BOTSPOT_CHATGPT_PLAN_UNAVAILABLE_RETRIES2BOTSPOT_CHATGPT_PLAN_UNAVAILABLE_BACKOFF_SECONDS2.0BOTSPOT_CHATGPT_PLAN_CAP_PAUSE_SECONDS3600BOTSPOT_CHATGPT_PLAN_REQUEST_TIMEOUT_SECONDS120BOTSPOT_CHATGPT_PLAN_RETURN_URL""The component also needs
BOTSPOT_MONGO_DATABASE_ENABLED=true.Public interface (for the whisper bot)
PlanResponse(text: str, model: str, usage: dict)LinkResult(user_id: int, sub: str, email: str, scopes: str)PlanLink(user_id, status, sub, email, scopes, linked_at, last_used_at, capped_until)The host falls back to its normal paid path on
PlanUsageCapped,PlanNeedsRelinkandPlanUnavailable. The plan path cannot setmax_tokens.Checks (local)
pytest tests/: 335 passed, 56 of them new intests/components/new/test_chatgpt_plan.py. Coverage is 73.7% total; the new package is 92–100% per file.ruff check botspotandruff format --check botspot: clean.vulture --min-confidence 80 botspot: clean.lizard -l python --CCN 20 -w botspot: clean. The highest CCN in the new code is 11 (parse_stream).pyright botspot/components/new/chatgpt_plan: 0 errors.Draft. Do not merge until Petr reviews it.
🤖 Generated with Claude Code