|
| 1 | +# SPEC — ChatGPT Subscription OAuth Direct Routing |
| 2 | + |
| 3 | +## Overview |
| 4 | +Implement an **experimental, default-disabled** ChatGPT subscription OAuth feature that allows the local CLI to route eligible OpenAI-model **streaming** requests directly to OpenAI instead of Codebuff backend routing, mirroring the prior Claude OAuth architecture pattern. |
| 5 | + |
| 6 | +## Protocol Assumptions (Explicit) |
| 7 | +Because this is unofficial/experimental, this implementation proceeds under the following explicit assumptions: |
| 8 | + |
| 9 | +1. OAuth authorize endpoint: `https://auth.openai.com/oauth/authorize` |
| 10 | +2. OAuth token endpoint: `https://auth.openai.com/oauth/token` |
| 11 | +3. Public client id is configurable constant, defaulting to Codex-compatible value from ecosystem references. |
| 12 | +4. PKCE (`S256`) is required. |
| 13 | +5. Redirect URI is pinned to: `http://localhost:1455/auth/callback` |
| 14 | +6. User can paste either: |
| 15 | + - raw authorization code, or |
| 16 | + - full callback URL containing code/state query params. |
| 17 | +7. Token response includes at least `access_token`, optional `refresh_token`, and expiry info (`expires_in` or equivalent). |
| 18 | +8. Refresh uses standard `grant_type=refresh_token`. |
| 19 | + |
| 20 | +If any assumption fails at runtime, the feature fails with explicit guidance and remains safely fallbackable only where policy allows. |
| 21 | + |
| 22 | +## Requirements |
| 23 | +1. Add ChatGPT OAuth feature set, default disabled behind `CHATGPT_OAUTH_ENABLED = false`. |
| 24 | +2. Add a new CLI command and mode: `/connect:chatgpt` with dedicated banner flow. |
| 25 | +3. Implement browser-based PKCE code-paste flow (no device-code flow in this iteration). |
| 26 | +4. Keep user-facing warning minimal (per user preference), while leaving code comments clearly marking experimental nature. |
| 27 | +5. Store ChatGPT OAuth credentials in local credentials JSON alongside existing credentials. |
| 28 | +6. Support env-var token override (power-user/automation use), but env var **must not bypass feature flag**. |
| 29 | +7. Add refresh-token support with concurrency guard (mutex) for persisted credentials. |
| 30 | +8. Direct routing scope is **streaming only** (`promptAiSdkStream` path); non-streaming and structured stay backend-routed. |
| 31 | +9. Add model allowlist for direct routing; include optimistic aliases: |
| 32 | + - `openai/gpt-5.3` |
| 33 | + - `openai/gpt-5.3-codex` |
| 34 | + - `openai/gpt-5.2` |
| 35 | + - `openai/gpt-5.2-codex` |
| 36 | + - plus selected nearby GPT/Codex IDs already present in repo config. |
| 37 | +10. Provide deterministic model normalization for direct requests (OpenRouter-style -> provider-native): |
| 38 | + - Example: `openai/gpt-5.3-codex` -> `gpt-5.3-codex` |
| 39 | + - Mapping table lives in constants and is used for prevalidation. |
| 40 | +11. Unsupported model handling must be deterministic and prevalidated: |
| 41 | + - if model is not in allowlist/mapping for direct route, fail with explicit unsupported-model error (no fallback). |
| 42 | +12. Fallback policy: |
| 43 | + - Rate-limit/overload classification: auto-fallback to Codebuff backend. |
| 44 | + - Auth errors (401/403): fail explicitly with reconnect guidance (no fallback). |
| 45 | + - All other direct errors: fail fast (no fallback), per user decision. |
| 46 | +13. Successful direct ChatGPT OAuth requests do **not** consume Codebuff credits. |
| 47 | +14. Add lightweight ChatGPT connection status surfacing in CLI (usage banner and/or bottom status line), without quota API dependency. |
| 48 | +15. Preserve existing Claude OAuth behavior unchanged. |
| 49 | +16. Add temporary OAuth validation script that tests auth URL generation + token exchange manually before/alongside full wiring. |
| 50 | +17. Add/update tests for credential parsing/storage/refresh, model gating, routing/fallback classification, and CLI command/mode wiring. |
| 51 | +18. Never log OAuth tokens in analytics or error logs. |
| 52 | + |
| 53 | +## Direct Request Transformation Rules |
| 54 | +Before sending direct streaming requests to OpenAI, enforce strict sanitization: |
| 55 | + |
| 56 | +1. Rewrite `model` from `openai/*` format to provider-native mapped id. |
| 57 | +2. Remove provider-specific/non-OpenAI fields (e.g., codebuff metadata/provider routing payloads). |
| 58 | +3. Preserve fields known to be valid for OpenAI-compatible chat completions. |
| 59 | +4. Do not inject Codex-specific required prefix by default in v1 (user preference), but structure code so optional future injection is easy. |
| 60 | + |
| 61 | +## Error Classification Table |
| 62 | +| Class | Detection | Behavior | |
| 63 | +|---|---|---| |
| 64 | +| Rate limit | HTTP 429 or message/body contains rate-limit indicators | Fallback to backend (if no output emitted yet) | |
| 65 | +| Auth | HTTP 401/403 or auth-token-invalid indicators | Fail with reconnect guidance; no fallback | |
| 66 | +| Unsupported model | Local allowlist/mapping precheck failure | Fail explicit unsupported-model error; no fallback | |
| 67 | +| Other | Network timeout, 5xx, malformed payload, unknown 4xx | Fail fast; no fallback | |
| 68 | + |
| 69 | +## Routing Scope |
| 70 | +1. Direct routing applies only to `promptAiSdkStream` eligible requests. |
| 71 | +2. `promptAiSdk` and `promptAiSdkStructured` remain backend-only for this iteration. |
| 72 | +3. Backend routing remains unchanged for all non-eligible models and when feature disabled/disconnected. |
| 73 | + |
| 74 | +## Credentials & Precedence Rules |
| 75 | +1. Credentials file schema extends with `chatgptOAuth` object. |
| 76 | +2. Precedence: env token override > persisted OAuth credentials > none. |
| 77 | +3. Env token produces synthetic non-refreshing credentials object. |
| 78 | +4. Persisted credentials refresh when expired/near-expiry (5-minute buffer). |
| 79 | +5. On refresh failure for persisted credentials, clear only `chatgptOAuth` entry (preserve other credentials). |
| 80 | + |
| 81 | +## Feature Gating Matrix |
| 82 | +1. `CHATGPT_OAUTH_ENABLED = false` |
| 83 | + - hide `/connect:chatgpt` command and banner UX |
| 84 | + - disable direct routing even if env token exists |
| 85 | +2. `CHATGPT_OAUTH_ENABLED = true` and credentials available |
| 86 | + - enable command/UI |
| 87 | + - enable direct routing for eligible models |
| 88 | + |
| 89 | +## Logging/Redaction Requirements |
| 90 | +1. Never log raw access tokens, refresh tokens, authorization headers, or token response payloads. |
| 91 | +2. If callback URL is logged for debugging, redact query values for `code`, `access_token`, `refresh_token`, and similar sensitive keys. |
| 92 | +3. Analytics properties must not include token-bearing strings. |
| 93 | + |
| 94 | +## Technical Approach |
| 95 | +1. Create `common/src/constants/chatgpt-oauth.ts`: |
| 96 | + - feature flag, endpoints, client id, redirect URI, env var name, model allowlist/mapping helpers. |
| 97 | +2. Export new constants via `common/src/constants/index.ts` so legacy `old-constants` re-export path includes them. |
| 98 | +3. Extend `sdk/src/env.ts` with ChatGPT OAuth env-token helper. |
| 99 | +4. Extend `sdk/src/credentials.ts` with ChatGPT OAuth schema+helpers mirroring Claude pattern. |
| 100 | +5. Create `cli/src/utils/chatgpt-oauth.ts` for PKCE start/open/exchange/disconnect/status. |
| 101 | +6. Create `cli/src/components/chatgpt-connect-banner.tsx` and auth-code handler. |
| 102 | +7. Wire CLI command/input mode/slash menu/router/banner registry for `connect:chatgpt`. |
| 103 | +8. Extend model provider (`sdk/src/impl/model-provider.ts`): |
| 104 | + - add ChatGPT direct route decision path for `openai/*` allowlisted models |
| 105 | + - add rate-limit cache helpers for ChatGPT path |
| 106 | + - build direct OpenAI-compatible language model with OAuth bearer auth |
| 107 | + - enforce strict body sanitization + model normalization in the direct path. |
| 108 | +9. Extend stream error handling (`sdk/src/impl/llm.ts`) for ChatGPT direct path with required fallback/fail rules and analytics. |
| 109 | +10. Extend app init (`cli/src/init/init-app.ts`) for background ChatGPT credential refresh when enabled. |
| 110 | +11. Add analytics events for ChatGPT OAuth request/rate-limit/auth-error. |
| 111 | +12. Update usage/status UI text to include ChatGPT connection state. |
| 112 | +13. Add temporary validation script (e.g., `scripts/chatgpt-oauth-validate.ts`) to exercise OAuth setup interactively. |
| 113 | + |
| 114 | +## Acceptance Criteria |
| 115 | +1. With feature disabled, `/connect:chatgpt` is unavailable and no direct routing occurs. |
| 116 | +2. With feature enabled, user can run `/connect:chatgpt`, complete browser flow, paste code/URL, and connect. |
| 117 | +3. Eligible streaming requests on allowlisted `openai/*` models use direct OAuth path. |
| 118 | +4. Direct request payloads are sanitized and model ids normalized before transmission. |
| 119 | +5. Rate-limited direct requests fallback to backend automatically. |
| 120 | +6. Auth failures produce reconnect guidance and do not fallback. |
| 121 | +7. Unsupported models fail immediately with explicit unsupported-model message. |
| 122 | +8. Successful direct requests skip Codebuff credit accounting path. |
| 123 | +9. Existing Claude OAuth flow remains behaviorally unchanged. |
| 124 | +10. New/updated tests pass for touched behavior. |
| 125 | +11. Temporary validation script can run and guide manual OAuth exchange checks. |
| 126 | + |
| 127 | +## Files to Create/Modify |
| 128 | +- Create: `common/src/constants/chatgpt-oauth.ts` |
| 129 | +- Create: `cli/src/utils/chatgpt-oauth.ts` |
| 130 | +- Create: `cli/src/components/chatgpt-connect-banner.tsx` |
| 131 | +- Create: `scripts/chatgpt-oauth-validate.ts` (temporary validation utility) |
| 132 | +- Modify: `common/src/constants/index.ts` |
| 133 | +- Modify: `common/src/constants/analytics-events.ts` |
| 134 | +- Modify: `sdk/src/env.ts` |
| 135 | +- Modify: `sdk/src/credentials.ts` |
| 136 | +- Modify: `sdk/src/impl/model-provider.ts` |
| 137 | +- Modify: `sdk/src/impl/llm.ts` |
| 138 | +- Modify: `sdk/src/index.ts` |
| 139 | +- Modify: `cli/src/utils/input-modes.ts` |
| 140 | +- Modify: `cli/src/components/input-mode-banner.tsx` |
| 141 | +- Modify: `cli/src/data/slash-commands.ts` |
| 142 | +- Modify: `cli/src/commands/command-registry.ts` |
| 143 | +- Modify: `cli/src/commands/router.ts` |
| 144 | +- Modify: `cli/src/chat.tsx` |
| 145 | +- Modify: `cli/src/components/usage-banner.tsx` |
| 146 | +- Modify: `cli/src/components/bottom-status-line.tsx` |
| 147 | +- Modify: `cli/src/init/init-app.ts` |
| 148 | +- Modify tests in SDK/CLI for new behavior. |
| 149 | + |
| 150 | +## Out of Scope |
| 151 | +1. Device-code auth flow. |
| 152 | +2. Legal/policy guarantees around undocumented endpoints. |
| 153 | +3. Full quota/usage API integration for ChatGPT subscription plans. |
| 154 | +4. Local callback server daemon beyond paste-based flow. |
| 155 | +5. Enabling feature by default. |
0 commit comments