Skip to content

Coach: sign in with ChatGPT to use your ChatGPT plan instead of an API key - #602

Merged
abdulsaheel merged 25 commits into
OpenStrap:mainfrom
MouryB:feat/sign-in-with-chatgpt
Nov 1, 2026
Merged

abdulsaheel merged 25 commits into
OpenStrap:mainfrom
MouryB:feat/sign-in-with-chatgpt

Conversation

@MouryB

@MouryB MouryB commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Why

The AI coach only works with an API key today, so anyone who already pays for ChatGPT pays twice to use it. On Sept 29 OpenAI opened Sign in with ChatGPT to open-source apps: an app can register itself, with no client secret and no approval step, and run eligible Responses API requests on the user's own ChatGPT plan (docs). This PR adds that as a coach provider. It's the official route, not the unofficial one that reuses Codex CLI tokens. OpenAI's docs specifically say not to call ChatGPT's backend-api.

What users see

Before: AI settings list Ollama, LM Studio, OpenAI, Anthropic and OpenRouter. Every cloud option needs a base URL and an API key.

After: a ChatGPT option sits between the local and cloud presets. Picking it hides the base URL, API and key fields and shows Continue with ChatGPT. After sign-in:

  • A one-time sheet says "You're using your ChatGPT plan", as OpenAI's UI guidelines require.
  • List models shows the account's models.
  • Manage usage opens chatgpt.com/settings/usage, and Sign out of ChatGPT revokes the sign-in.
  • The coach header reads "<model> · Using ChatGPT plan".

The coach, the briefings and the journal all use it, because they share CoachEngine.postChat.

How it works

  • New file lib/coach/coach_chatgpt.dart: OAuth with PKCE, state and nonce, plus dynamic client registration (dynamic_agent_client with a persisted urn:uuid host id). The issued client id is saved and reused on later sign-ins, as OpenAI asks.
  • Login callback: the redirect goes to a listener on 127.0.0.1, as the docs require. The page opens with LaunchMode.inAppBrowserView (SFSafariViewController / Custom Tab) so the app stays in the foreground and the listener stays alive; on iOS the full Safari app would suspend us. There's a Cancel sign-in button, and leaving the screen cancels too, because the app gets no signal when the user just closes the browser.
  • Tokens: kept in the keychain with the same first_unlock accessibility as the BYOK key, so a locked-phone background relaunch can still read them. Refresh is serialized so concurrent callers share one rotation. A dead refresh token signs the user out; a network or server error keeps the credentials.
  • Requests: one branch in postChat sends stream: true and store: false, buffers the SSE stream and passes the response.completed payload to the existing CoachResponses.reply parser. system input items become developer (the route rejects system). Sampling params were already never sent on Responses. The fail-closed request size ceiling still applies.
  • Errors: OpenAI's subscription_sharing_* codes become plain messages. Billing errors stop the request and never fall back to an API key. Signing out leaves the coach in ChatGPT mode, unconfigured, instead of quietly switching back to a saved key.
  • Config: CoachConfig gains usesChatGpt, and configured, apiBase, api and keyUnreadable follow it, so the existing consent prompts (ECG, briefing) see api.openai.com as a cloud endpoint. The BYOK settings stay untouched for switching back.
  • Reasoning replay: keyed separately (…#chatgpt), so a plan turn never replays encrypted reasoning from an API-key turn on the same base.
  • Android: a CustomTabsService <queries> entry, so Android 11+ finds a Custom Tab instead of falling back to a WebView.
  • Docs: a new section in guides/AI_COACH.md, and PRIVACY.md updated.

The ID token's issuer, audience, expiry and nonce are checked. The signature isn't: the token arrives straight from the token endpoint over TLS in a PKCE code exchange, which OIDC Core §3.1.3.7(6) allows. I can add JWKS verification with pointycastle if you'd rather have it.

Testing

  • flutter analyze is clean (Flutter 3.41.6, the CI pin).
  • New test/coach_chatgpt_test.dart, 11 tests:
    • a full sign-in round trip through a real loopback listener: registration params, a stray wrong-state hit rejected, code exchange, reuse of the registration on the second sign-in
    • declined consent, and cancel
    • ID token claim checks
    • refresh: one rotation for concurrent callers, dead token vs. transient failure
    • request shape: endpoint, bearer, stream/store, no temperature, system→developer
    • SSE parsing, usage-limit and mid-stream failures, and a cut-off stream
    • no plan scope means not configured
    • model list filtering
  • russian_review_findings_test: the new card made the setup list taller, which left "List models" a few px off the 800×600 test surface after scrollUntilVisible. I added ensureVisible before the tap.
  • The full suite passes apart from failures that also happen on a clean main on macOS: three timezone-dependent tests, which pass with TZ=UTC, and health_workout_export_delete_gate_test, because HealthExport.isApple is true on a macOS host.

Tested on a device: iPhone 16 Pro Max on iOS 27.0.1, with a real ChatGPT account. Sign-in works end to end, and coach replies come back on the plan. The first device run found that the route's response.completed event can carry an empty output, which made every reply fail with "Empty response from provider". ec6035b builds the output from response.output_item.done and adds a test for it. ec6035b also addresses all 13 CodeRabbit/Sourcery findings.

Summary by Sourcery

Enable users to connect their ChatGPT plan to the AI coach through OpenAI’s official sign-in flow instead of configuring an API key.

New Features:

  • Add Sign in with ChatGPT as a coach provider, allowing eligible coach, briefing, and journal requests to use the user's ChatGPT plan without an API key.
  • Provide ChatGPT sign-in, model selection, usage management, sign-out, and plan-status feedback in AI Coach settings.

Bug Fixes:

  • Prevent ChatGPT plan requests from falling back to an API key after usage, authentication, or billing failures.
  • Handle streamed Responses API completions whose completed event omits output items.

Enhancements:

  • Integrate ChatGPT plan authentication, token lifecycle management, request handling, and provider state throughout the coach engine and configuration.
  • Preserve existing BYOK settings while clearly identifying ChatGPT-plan usage and isolating reasoning replay data by account and payment source.

Build:

  • Enable Android Custom Tab discovery for the ChatGPT sign-in flow.

Documentation:

  • Document ChatGPT plan setup, usage, sign-out, data handling, and privacy implications for the AI Coach.

Tests:

  • Add coverage for ChatGPT OAuth, token refresh and revocation, request and SSE handling, model filtering, lifecycle races, and failure scenarios.
  • Update the Russian review UI test to keep the model-list action visible after the expanded setup screen.

Chores:

  • Clear ChatGPT credentials during app resets and include configured ChatGPT plans when scheduling AI reminders.

Summary by CodeRabbit

  • New Features
    • AI Coach can connect through an eligible ChatGPT Plus or Pro plan, with sign-in and model selection available without an API key. API-key setup remains available.
    • Coach requests can use plan usage and stop when the plan limit is reached, without switching to API billing. Sign-in tokens are stored securely on the device and sent only to OpenAI.
    • Coach settings let you manage plan usage or sign out. Resetting all data attempts to clear ChatGPT sign-in data and warns you if sign-in may remain.
  • Documentation
    • Updated guidance explains that ChatGPT plan requests send data to OpenAI under your ChatGPT account, and covers applicable data controls, terms, usage policies, and how to manage or disconnect the connection.

Loading
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.

3 participants