# ChatGPT OAuth Authentication

Add `chatgpt` as a native provider option, leveraging LiteLLM's built-in ChatGPT
provider which handles OAuth device-code flow, token storage, and refresh
automatically.

## Background

LiteLLM has a `chatgpt` provider that authenticates via OAuth device-code flow
against `auth.openai.com`. Tokens are stored at `~/.config/litellm/chatgpt/auth.json`
and refreshed transparently. The provider string format is `chatgpt/{model_id}`.

Supported models include `gpt-5.3-codex`, `gpt-5.2-codex`, `gpt-5.2`,
`gpt-5.1-codex-max`, `gpt-5.1-codex-mini`. The `chatgpt/` prefix routes through LiteLLM's ChatGPT
handler which injects the OAuth bearer token, sets the correct API base
(`chatgpt.com/backend-api/codex`), and handles the Codex system prompt injection.

No API key is needed — the user authenticates interactively via a device code
printed to stderr, then tokens are cached on disk.

## Changes

### 1. `agent.py` — `--provider` choices (line ~1512)

Add `"chatgpt"` to the choices list:

```python
choices=["lmstudio", "huggingface", "openrouter", "generic", "chatgpt"],
```

Update the help text to mention it.

### 2. `agent.py` — `call_llm()` provider routing (line ~1187)

Add a branch for `chatgpt`:

```python
elif provider == "chatgpt":
    bare_id = model_id.removeprefix("chatgpt/")
    model_str = f"chatgpt/{bare_id}"
    kwargs = {}
    if api_key:
        kwargs["api_key"] = api_key
    if base_url:
        kwargs["api_base"] = base_url
```

ChatGPT auth is handled internally by LiteLLM's authenticator — no `api_key` or
`api_base` needs to be injected by Swival. But if the user provides them
explicitly (e.g. for a proxy setup), pass them through.

Double-prefix guard: `removeprefix("chatgpt/")` ensures that passing
`chatgpt/gpt-5.3-codex` as the model_id doesn't produce `chatgpt/chatgpt/...`.
This mirrors the openrouter pattern.

### 3. `agent.py` — `resolve_provider()` (line ~1935)

Add a branch before the final `else`:

```python
elif provider == "chatgpt":
    if not model:
        raise ConfigError(
            "--model is required when --provider is chatgpt. "
            "Available models: gpt-5.3-codex, gpt-5.2-codex, gpt-5.2, "
            "gpt-5.1-codex-max, gpt-5.1-codex-mini. "
            "See https://docs.litellm.ai/docs/providers/chatgpt"
        )
    api_base = base_url  # None unless user overrides
    model_id = model
    resolved_key = api_key  # None is fine — LiteLLM authenticates via OAuth
    # Try LiteLLM's model metadata for context size; fall back to None so
    # compaction uses the global default rather than a stale hardcoded value.
    context_length = max_context_tokens
    if context_length is None:
        try:
            import litellm
            info = litellm.get_model_info(f"chatgpt/{model_id.removeprefix('chatgpt/')}")
            context_length = info.get("max_input_tokens")
        except Exception:
            pass
```

Key design decisions:

- **No default model.** ChatGPT model names are version-pinned (no rolling
  alias like `codex-mini-latest` exists for the full models). Pinning a default
  means new installs silently break when model names change. Instead, require
  `--model` and fail with an actionable error listing valid options.

- **Context length from LiteLLM metadata.** LiteLLM's bundled cost map includes
  `max_input_tokens` for all registered `chatgpt/` models (currently 128k for
  all of them). Querying it at runtime avoids hardcoding a value that could
  drift. If the lookup fails (model not in cost map yet), `context_length` stays
  `None` and Swival's existing fallback behavior applies.

- **No API key required.** LiteLLM's `Authenticator` handles device-code login
  interactively. If the user passes `--api-key`, it's forwarded (useful for
  pre-obtained bearer tokens or proxy setups).

### 4. `config.py` — `generate_config()` (line ~527)

Update the provider comment to include `"chatgpt"`:

```python
'# provider = "lmstudio"          # "lmstudio" | "huggingface" | "openrouter" | "generic" | "chatgpt"',
```

### 5. Documentation updates

Every file that lists the provider set needs updating:

| File | Line(s) | What to update |
|---|---|---|
| `README.md` | ~13-16 | Add ChatGPT to the provider link list |
| `README.md` | ~68-71 | Add a ChatGPT quickstart block after the generic provider section |
| `README.md` | ~102-107 | Mention ChatGPT in the features list |
| `docs.md/getting-started.md` | ~35, ~106 | Add ChatGPT section with example |
| `docs.md/usage.md` | ~78 | Add `chatgpt` to the valid values list |
| `docs.md/providers.md` | ~3, ~87+ | Add a full ChatGPT section (OAuth flow, models, env vars) |
| `docs.md/reports.md` | ~64 | Add `chatgpt` to the provider value list |
| `docs.md/customization.md` | ~23 | Mention chatgpt as an option in config example |
| `CLAUDE.md` | ~61, ~99-102 | Add chatgpt provider string format |
| `AGENTS.md` | ~42 | Add chatgpt routing info |

The `docs.md/providers.md` section should document:
- OAuth device-code flow (what the user sees on first run)
- Token storage location (`~/.config/litellm/chatgpt/auth.json`)
- Available models and their context limits
- Relevant env vars (`CHATGPT_TOKEN_DIR`, `CHATGPT_API_BASE`)
- That `--model` is required (no default)

### 6. `session.py` — No changes needed

The `Session` class already accepts arbitrary `provider` strings and passes them
through to `resolve_provider()`. No modification required.

### 7. `fmt.py` — Auth status message

Skip for now. LiteLLM prints its own device-code prompt and verification URL to
stderr. We can add polish later if the UX feels rough.

### 8. Tests — `tests/test_provider.py`

Add tests mirroring the existing patterns:

**`TestCallLlmRouting`:**
- `test_chatgpt_routing` — verify `model == "chatgpt/gpt-5.3-codex"`,
  no `api_key` or `api_base` in kwargs by default.
- `test_chatgpt_with_explicit_key` — verify `api_key` is passed through.
- `test_chatgpt_with_base_url` — verify `api_base` is set when provided.

**`TestModelNormalization`:**
- `test_chatgpt_no_double_prefix` — `chatgpt/gpt-5.3-codex` as input
  produces `chatgpt/gpt-5.3-codex` (not `chatgpt/chatgpt/...`).
- `test_chatgpt_double_prefix_stripped` — `chatgpt/chatgpt/gpt-5.3-codex`
  as input produces `chatgpt/gpt-5.3-codex` (defensive, mirrors openrouter).

**`TestCLIValidation`:**
- `test_chatgpt_requires_model` — verify error when `--model` is omitted.

**`TestAPIKeyResolution`:**
- `test_chatgpt_no_key_ok` — verify no error when API key is absent.

**`TestProviderPathIsolation`:**
- `test_chatgpt_never_calls_discover_or_configure` — verify lmstudio-specific
  functions are not called.

**`TestResolveProviderDirect` (new class):**
Direct unit tests for `resolve_provider()` with `provider="chatgpt"`:
- `test_chatgpt_returns_model_id` — model passed through as-is.
- `test_chatgpt_no_model_raises` — `ConfigError` when model is `None`.
- `test_chatgpt_resolved_key_none_when_no_key` — `resolved_key` is `None`.
- `test_chatgpt_resolved_key_passthrough` — `resolved_key` equals provided key.
- `test_chatgpt_api_base_passthrough` — `api_base` is `None` by default,
  equals `base_url` when provided.
- `test_chatgpt_context_from_litellm_metadata` — when `max_context_tokens`
  is `None`, context_length comes from `litellm.get_model_info()`.
- `test_chatgpt_context_override` — `max_context_tokens` takes precedence.

## Files touched

| File | What changes |
|---|---|
| `swival/agent.py` | Add `"chatgpt"` to `--provider` choices, `call_llm()` routing, `resolve_provider()` branch |
| `swival/config.py` | Update provider comment in `generate_config()` |
| `tests/test_provider.py` | Add ChatGPT routing, normalization, validation, key resolution, path isolation, and direct `resolve_provider` unit tests |
| `README.md` | Add ChatGPT provider to overview, quickstart, and features |
| `docs.md/getting-started.md` | Add ChatGPT getting-started section |
| `docs.md/usage.md` | Add `chatgpt` to `--provider` valid values |
| `docs.md/providers.md` | Add full ChatGPT provider section (OAuth, models, env vars) |
| `docs.md/reports.md` | Add `chatgpt` to provider value list |
| `docs.md/customization.md` | Mention chatgpt in config example |
| `CLAUDE.md` | Add chatgpt to provider list and string formats |
| `AGENTS.md` | Add chatgpt routing info |

## What we do NOT need to do

- **No OAuth implementation.** LiteLLM's `litellm.llms.chatgpt.authenticator`
  handles the entire device-code flow, token refresh, and disk caching.
- **No new dependencies.** LiteLLM is already a dependency.
- **No new config keys.** The existing `provider`, `model`, `api_key`, `base_url`
  are sufficient.
- **No environment variable handling.** LiteLLM reads `CHATGPT_TOKEN_DIR` and
  `CHATGPT_API_BASE` internally if set. Swival doesn't need to broker them.
- **No default model.** Version-pinned names change; require explicit `--model`.
- **No hardcoded context window.** Query LiteLLM metadata at runtime instead.

## Usage

```sh
# First run — triggers device-code auth in terminal
swival --provider chatgpt --model gpt-5.3-codex "explain this codebase"

# Non-coding model
swival --provider chatgpt --model gpt-5.2 "summarize these docs"

# Config file (swival.toml)
# provider = "chatgpt"
# model = "gpt-5.3-codex"
```
