Skip to content

Commit 9295e16

Browse files
committed
Add /connect:chatgpt
1 parent fae9205 commit 9295e16

33 files changed

+2496
-195
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# LESSONS — ChatGPT OAuth Direct Routing
2+
3+
Session: `.agents/sessions/03-02-14:07-chatgpt-oauth-direct/`
4+
5+
## What went well
6+
- Building this feature behind a strict feature flag (`CHATGPT_OAUTH_ENABLED=false`) reduced rollout risk while allowing full end-to-end wiring.
7+
- Reusing the Claude OAuth architectural pattern (credentials helpers, refresh mutex, routing split) accelerated implementation without coupling the two providers.
8+
- Splitting policy logic into `classifyChatGptOAuthStreamError` made fallback/auth/fail-fast behavior easier to test and reason about.
9+
- Adding focused CLI tests for `/connect:chatgpt` gating and utility sanitization caught regression risk early.
10+
11+
## Current confidence / known gaps
12+
- Runtime ChatGPT stream policy is **partially tested**: `classifyChatGptOAuthStreamError` is covered, but we do not yet have full behavioral tests for `promptAiSdkStream` recursion branches (actual fallback recursion and post-partial-output behavior).
13+
- CLI routing coverage is strongest for **feature-flag OFF** paths; flag-ON auth-code routing should get explicit dedicated tests in a future pass.
14+
15+
## What was tricky
16+
- The repo had unrelated local drift during implementation; explicit scope cleanup (`git checkout -- <unrelated files>`) was necessary to avoid accidental cross-feature commits.
17+
- CLI module mocking is path-sensitive. Test modules under `cli/src/commands/__tests__` must mock sibling modules with correct relative paths (e.g. `../../state/chat-store`), or mocks silently fail.
18+
- Over-mocking analytics can break transitive imports (`setAnalyticsErrorLogger` export expectations). A safe pattern is spreading real analytics exports and overriding only `trackEvent`.
19+
20+
## Unexpected behaviors / gotchas
21+
- A staged unrelated file can survive despite working-tree revert; both staged and worktree states must be checked before final handoff.
22+
- “Looks correct” tests can still miss runtime branches if they only validate helper classification, not route wiring; reviewer loops were useful to force coverage on practical paths.
23+
- For OAuth tooling/scripts, sanitize error text aggressively. Returning status-only errors avoids accidental token payload leakage.
24+
25+
## Useful patterns discovered
26+
- Keep direct-provider routing stream-only initially; explicitly forcing non-streaming/structured calls to backend avoided broad compatibility risk.
27+
- Use deterministic model allowlist + normalization mapping in constants to avoid relying on provider-side parsing/errors for unsupported models.
28+
- Treat temporary protocol validation scripts as first-class validation artifacts: they are valuable for real-account smoke checks without coupling to full CLI runtime.
29+
30+
## Temporary script disposition
31+
- `scripts/chatgpt-oauth-validate.ts` is currently kept as a **dev utility** for manual protocol revalidation while the feature remains experimental/off by default.
32+
- Removal criteria: if protocol endpoints are either officially documented or the CLI flow gets stable automated integration coverage, this script can be retired.
33+
34+
## Repeatable security verification
35+
- For redaction checks, run targeted searches against changed code/log handling paths for sensitive markers before handoff, e.g. `access_token`, `refresh_token`, and `Authorization: Bearer`.
36+
- Keep surfaced token exchange errors status-only and avoid echoing raw provider response bodies.
37+
38+
## Follow-up improvements worth considering
39+
- Add deeper runtime-behavior tests for `promptAiSdkStream` recursive fallback branches (not just policy classifier).
40+
- Add explicit CLI test for flag-ON connect flow path once flag toggling is test-harness friendly.
41+
- If feature graduates from experimental, add richer direct-path observability while preserving strict token redaction.
42+
- Add periodic protocol drift checks (authorize/token/callback PKCE assumptions) before enabling the feature flag in production defaults.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# PLAN — ChatGPT Subscription OAuth Direct Routing
2+
3+
## Implementation Steps
4+
1. **Add shared ChatGPT OAuth constants**
5+
- Create `common/src/constants/chatgpt-oauth.ts` with:
6+
- feature flag (`CHATGPT_OAUTH_ENABLED=false`)
7+
- endpoints/client id/redirect URI/env var
8+
- model allowlist + normalization helpers
9+
- Export through `common/src/constants/index.ts`.
10+
11+
2. **Build core OAuth utility + temporary protocol validation script (early gate)**
12+
- Create `cli/src/utils/chatgpt-oauth.ts` with PKCE URL generation, browser-open helper, pasted code/URL parsing, token exchange helper.
13+
- Create `scripts/chatgpt-oauth-validate.ts` to test OAuth URL generation + paste parsing + token exchange interaction.
14+
- **Run this script before full integration** as go/no-go checkpoint for endpoint assumptions.
15+
16+
3. **Add SDK env + credential support**
17+
- Extend `sdk/src/env.ts` with `getChatGptOAuthTokenFromEnv()`.
18+
- Extend `sdk/src/credentials.ts` with `chatgptOAuth` schema and helpers:
19+
- get/save/clear
20+
- valid-check + refresh mutex
21+
- get-valid-with-refresh
22+
- Preserve all non-target credentials in read/write operations.
23+
24+
4. **Add CLI connect flow UI and command routing**
25+
- Create `cli/src/components/chatgpt-connect-banner.tsx` with state machine + `handleChatGptAuthCode`.
26+
- Update input modes (`connect:chatgpt`) and banner registry.
27+
- Add `/connect:chatgpt` command + alias handling and slash command entry (feature-gated).
28+
- Extend router to process pasted auth code in `connect:chatgpt` mode.
29+
- Verify command visibility: hidden when flag OFF, present when flag ON.
30+
31+
5. **Implement direct routing primitives in model-provider (decomposed)**
32+
- 5.1 Add ChatGPT direct eligibility checks (feature flag + creds + model scope + skip flag + rate-limit cache state).
33+
- 5.2 Add model normalization + prevalidation helpers (OpenRouter-style -> provider-native).
34+
- 5.3 Add strict payload sanitization helper for direct requests.
35+
- 5.4 Add ChatGPT OAuth direct model construction using OpenAI-compatible transport.
36+
- 5.5 Add ChatGPT rate-limit cache helpers (parallel to Claude cache pattern).
37+
- Keep Claude OAuth path unchanged.
38+
39+
6. **Update stream execution + fallback/error policy**
40+
- Extend `sdk/src/impl/llm.ts` to:
41+
- recognize ChatGPT direct route usage
42+
- emit ChatGPT OAuth analytics
43+
- fallback only on rate-limit errors
44+
- fail with reconnect guidance on auth errors
45+
- fail fast for all other direct errors
46+
- skip cost accounting for successful ChatGPT direct requests
47+
- avoid fallback once output has already streamed
48+
49+
7. **Wire startup refresh + CLI status surfacing**
50+
- Update `cli/src/init/init-app.ts` for background ChatGPT OAuth credential refresh when enabled.
51+
- Update `cli/src/chat.tsx`, `cli/src/components/bottom-status-line.tsx`, and `cli/src/components/usage-banner.tsx` to surface ChatGPT connection/active status.
52+
53+
8. **Add analytics constants + SDK exports**
54+
- Extend `common/src/constants/analytics-events.ts` with ChatGPT OAuth request/rate-limit/auth-error events.
55+
- Ensure SDK exports newly needed helper(s) in `sdk/src/index.ts`.
56+
57+
9. **Add/adjust tests (explicit matrix)**
58+
- SDK credentials tests:
59+
- env precedence
60+
- persisted read/write/clear
61+
- refresh success/failure + mutex
62+
- Model-provider tests:
63+
- rate-limit cache lifecycle
64+
- allowlist prevalidation + unsupported-model error
65+
- normalization behavior for mapped/unknown variants
66+
- LLM routing/fallback tests (targeted):
67+
- 429 fallback
68+
- 401/403 no-fallback + reconnect path
69+
- timeout/5xx fail-fast
70+
- no fallback after content emitted
71+
- CLI tests/wiring checks:
72+
- command/mode visibility by feature flag
73+
- connect mode routing and handler call.
74+
- Non-streaming/structured guard check:
75+
- confirm backend-only behavior unchanged.
76+
77+
10. **Validation and cleanup decision for temporary script**
78+
- Run targeted tests/typechecks for touched packages.
79+
- Run OAuth validation script in manual mode (with your account interaction if needed).
80+
- Decide and apply final disposition of temporary script:
81+
- keep as dev utility, or
82+
- remove before finalization.
83+
84+
11. **Security/redaction verification**
85+
- Validate no token values are logged in direct feature code paths.
86+
- Grep/check for accidental logging of authorization headers, token payload fields, or raw callback query params.
87+
88+
## Dependencies / Ordering
89+
- Step 1 must be first.
90+
- Step 2 must run before deep integration (early protocol validation gate).
91+
- Step 3 precedes Steps 5–7.
92+
- Step 4 can run in parallel with Step 3 after constants/util setup.
93+
- Step 5 must precede Step 6.
94+
- Step 8 can be implemented alongside Steps 5–6 but must complete before final validation.
95+
- Step 9 follows core implementation completion.
96+
- Steps 10–11 are final validation/cleanup/security passes.
97+
98+
## Risk Areas
99+
1. **Unofficial OAuth contract drift** — endpoint/field incompatibility can break token exchange.
100+
2. **Direct payload compatibility** — strict sanitization must retain required OpenAI fields.
101+
3. **Error classification correctness** — misclassification can violate requested fallback policy.
102+
4. **Model normalization accuracy** — wrong mapping yields avoidable provider failures.
103+
5. **Token redaction** — avoid leakage in logs, errors, or analytics payloads.
104+
6. **Streaming boundary behavior** — fallback must not happen after partial output is emitted.
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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

Comments
 (0)