Add first-class ChatGPT subscription provider support (#881)

* Add first-class ChatGPT subscription provider support

* Address PR feedback: move uuid import to top, reuse parsed config in up()

* Add ChatGPT token watchdog for seamless long-lived sessions

* Address PR feedback: error on stream=false for ChatGPT, fix auth file permissions

* Replace ChatGPT watchdog/restart with passthrough_auth

---------

Co-authored-by: Musa Malik <musam@uw.edu>
This commit is contained in:
Musa 2026-04-23 15:34:44 -07:00 committed by GitHub
parent aa726b1bba
commit 78dc4edad9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 693 additions and 20 deletions

View file

@ -0,0 +1,61 @@
# ChatGPT Subscription Routing
Route requests through your ChatGPT Plus/Pro subscription using Plano. Uses the OpenAI Responses API under the hood, targeting `chatgpt.com/backend-api/codex/responses`.
## Setup
### 1. Authenticate with ChatGPT
```bash
planoai chatgpt login
```
This opens a device code flow — visit the URL shown and enter the code. Tokens are saved to `~/.plano/chatgpt/auth.json`.
### 2. Start Plano
```bash
planoai up config.yaml
```
### 3. Send a request
```bash
curl http://localhost:12000/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.2",
"input": "Hello, what model are you?"
}'
```
Or use the test script:
```bash
bash test_chatgpt.sh
```
## How it works
- `chatgpt/gpt-5.2` in the config tells Plano to use the ChatGPT subscription provider
- Plano reads OAuth tokens from `~/.plano/chatgpt/auth.json` (auto-refreshes if expired)
- Requests are proxied to `https://chatgpt.com/backend-api/codex/responses` with the required headers:
- `Authorization: Bearer <access_token>`
- `ChatGPT-Account-Id: <account_id>`
- `originator: codex_cli_rs`
- `session_id: <uuid>`
## Available models
```
chatgpt/gpt-5.4
chatgpt/gpt-5.3-codex
chatgpt/gpt-5.2
```
## Managing credentials
```bash
planoai chatgpt status # Check auth status
planoai chatgpt logout # Remove stored credentials
```

View file

@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""Interactive chat with a model through Plano using the OpenAI SDK."""
import sys
from openai import OpenAI
client = OpenAI(base_url="http://localhost:12000/v1", api_key="unused")
def run_chat(model):
print(f"Chatting with {model} via Plano (Ctrl+C to quit)\n")
history = []
while True:
try:
user_input = input("you> ")
except (KeyboardInterrupt, EOFError):
print("\nbye")
break
if not user_input.strip():
continue
history.append({"role": "user", "content": user_input})
stream = client.responses.create(model=model, input=history, stream=True)
print(f"{model}> ", end="", flush=True)
full = ""
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
full += event.delta
print()
history.append({"role": "assistant", "content": full})
if __name__ == "__main__":
model = sys.argv[1] if len(sys.argv) > 1 else "gpt-5.2"
run_chat(model)

View file

@ -0,0 +1,9 @@
version: v0.3.0
listeners:
- type: model
name: model_listener
port: 12000
model_providers:
- model: chatgpt/*

View file

@ -0,0 +1,18 @@
#!/bin/bash
# Test ChatGPT subscription routing through Plano
# Prerequisites: planoai chatgpt login && planoai up config.yaml
set -e
echo "Testing ChatGPT subscription via Plano Responses API..."
echo ""
curl -s http://localhost:12000/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.2",
"input": "What is 2 + 2? Reply in one word."
}' | python3 -m json.tool
echo ""
echo "Done."