diff --git a/CHANGELOG.md b/CHANGELOG.md index ec29e07..4d6de33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to this project are documented here. Dates use the ISO format (YYYY-MM-DD). +## [4.5.0] - 2026-07-06 + +**Model release**: GPT-5.4 and GPT-5.5 support. + +### Added + +| Preset | Context | Output | Chat reasoning | Codex reasoning | Same backend model as chat? | +|---|---|---|---|---|---| +| GPT-5.5 | 1.05M | 128K | none/low/medium/high/xhigh | low/medium/high/xhigh | **Yes** — no separate `-codex` deployment | +| GPT-5.4 | 1.05M | 128K | none/low/medium/high/xhigh | low/medium/high/xhigh | **Yes** — no separate `-codex` deployment | +| GPT-5.4 Mini | 400K | 128K | none/low/medium/high/xhigh | — | n/a (chat-only, defaults to `low`) | +| GPT-5.4 Nano | 400K | 128K | none/low/medium/high/xhigh | — | n/a (chat-only, defaults to `low`) | + +Confirmed via testing: GPT-5.4/5.5, unlike GPT-5.1/5.2, have no distinct `-codex` backend deployment. The OA-chat/OA-codex split is purely which system prompt and reasoning defaults the plugin applies — same underlying model either way. + +### Changed +- `getReasoningConfig` now derives `xhigh`/`none` support from the GPT-5.x point-release number, not a hardcoded flag per version — future point releases with the same schema need only new `MODEL_MAP`/config entries. +- New `resolveWireModel()` in `model-map.ts` sends the real model name (`gpt-5.4`/`gpt-5.5`) on the wire for the OA-codex presets, instead of the rejected `gpt-5.4-codex`/`gpt-5.5-codex` strings. + ## [4.4.0] - 2026-01-09 **Maintenance release**: OAuth success page version sync. diff --git a/README.md b/README.md index 6708114..049e11b 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,12 @@ npx -y opencode-openai-codex-auth@latest --uninstall --all ``` --- ## 📦 Models +- **gpt-5.5 (OA-chat)** (none/low/medium/high/xhigh) +- **gpt-5.5 (OA-codex)** (low/medium/high/xhigh) — same backend model as OA-chat, no separate `-codex` deployment +- **gpt-5.4 (OA-chat)** (none/low/medium/high/xhigh) +- **gpt-5.4 (OA-codex)** (low/medium/high/xhigh) — same backend model as OA-chat, no separate `-codex` deployment +- **gpt-5.4-mini** (none/low/medium/high/xhigh) +- **gpt-5.4-nano** (none/low/medium/high/xhigh) - **gpt-5.2** (none/low/medium/high/xhigh) - **gpt-5.2-codex** (low/medium/high/xhigh) - **gpt-5.1-codex-max** (low/medium/high/xhigh) @@ -58,7 +64,7 @@ Minimal configs are not supported for GPT‑5.x; use the full configs above. --- ## ✅ Features - ChatGPT Plus/Pro OAuth authentication (official flow) -- 22 model presets across GPT‑5.2 / GPT‑5.2 Codex / GPT‑5.1 families +- 50 model presets across GPT‑5.5 / GPT‑5.4 / GPT‑5.2 / GPT‑5.1 families - Variant system support (v1.0.210+) + legacy presets - Multimodal input enabled for all models - Usage‑aware errors + automatic token refresh diff --git a/config/opencode-legacy.json b/config/opencode-legacy.json index 5381226..0585ea3 100644 --- a/config/opencode-legacy.json +++ b/config/opencode-legacy.json @@ -15,6 +15,342 @@ "store": false }, "models": { + "gpt-5.5-none": { + "name": "GPT 5.5 None (OA-chat)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "none", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.5-low": { + "name": "GPT 5.5 Low (OA-chat)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.5-medium": { + "name": "GPT 5.5 Medium (OA-chat)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.5-high": { + "name": "GPT 5.5 High (OA-chat)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.5-xhigh": { + "name": "GPT 5.5 Extra High (OA-chat)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.5-codex-low": { + "name": "GPT 5.5 Low (OA-codex)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.5-codex-medium": { + "name": "GPT 5.5 Medium (OA-codex)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.5-codex-high": { + "name": "GPT 5.5 High (OA-codex)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.5-codex-xhigh": { + "name": "GPT 5.5 Extra High (OA-codex)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-none": { + "name": "GPT 5.4 None (OA-chat)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "none", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-low": { + "name": "GPT 5.4 Low (OA-chat)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-medium": { + "name": "GPT 5.4 Medium (OA-chat)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-high": { + "name": "GPT 5.4 High (OA-chat)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-xhigh": { + "name": "GPT 5.4 Extra High (OA-chat)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-codex-low": { + "name": "GPT 5.4 Low (OA-codex)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-codex-medium": { + "name": "GPT 5.4 Medium (OA-codex)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-codex-high": { + "name": "GPT 5.4 High (OA-codex)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-codex-xhigh": { + "name": "GPT 5.4 Extra High (OA-codex)", + "limit": { "context": 1050000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-mini-none": { + "name": "GPT 5.4 Mini None (OA-chat)", + "limit": { "context": 400000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "none", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-mini-low": { + "name": "GPT 5.4 Mini Low (OA-chat)", + "limit": { "context": 400000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-mini-medium": { + "name": "GPT 5.4 Mini Medium (OA-chat)", + "limit": { "context": 400000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-mini-high": { + "name": "GPT 5.4 Mini High (OA-chat)", + "limit": { "context": 400000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-mini-xhigh": { + "name": "GPT 5.4 Mini Extra High (OA-chat)", + "limit": { "context": 400000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-nano-none": { + "name": "GPT 5.4 Nano None (OA-chat)", + "limit": { "context": 400000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "none", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-nano-low": { + "name": "GPT 5.4 Nano Low (OA-chat)", + "limit": { "context": 400000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-nano-medium": { + "name": "GPT 5.4 Nano Medium (OA-chat)", + "limit": { "context": 400000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-nano-high": { + "name": "GPT 5.4 Nano High (OA-chat)", + "limit": { "context": 400000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, + "gpt-5.4-nano-xhigh": { + "name": "GPT 5.4 Nano Extra High (OA-chat)", + "limit": { "context": 400000, "output": 128000 }, + "modalities": { "input": ["text", "image"], "output": ["text"] }, + "options": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium", + "include": ["reasoning.encrypted_content"], + "store": false + } + }, "gpt-5.2-none": { "name": "GPT 5.2 None (OAuth)", "limit": { diff --git a/config/opencode-modern.json b/config/opencode-modern.json index 9161024..cb3fa12 100644 --- a/config/opencode-modern.json +++ b/config/opencode-modern.json @@ -15,6 +15,254 @@ "store": false }, "models": { + "gpt-5.5": { + "name": "GPT 5.5 (OA-chat)", + "limit": { + "context": 1050000, + "output": 128000 + }, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "variants": { + "none": { + "reasoningEffort": "none", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "low": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "medium": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "high": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + }, + "xhigh": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + } + } + }, + "gpt-5.5-codex": { + "name": "GPT 5.5 (OA-codex)", + "limit": { + "context": 1050000, + "output": 128000 + }, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "variants": { + "low": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "medium": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "high": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + }, + "xhigh": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + } + } + }, + "gpt-5.4": { + "name": "GPT 5.4 (OA-chat)", + "limit": { + "context": 1050000, + "output": 128000 + }, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "variants": { + "none": { + "reasoningEffort": "none", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "low": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "medium": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "high": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + }, + "xhigh": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + } + } + }, + "gpt-5.4-codex": { + "name": "GPT 5.4 (OA-codex)", + "limit": { + "context": 1050000, + "output": 128000 + }, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "variants": { + "low": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "medium": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "high": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + }, + "xhigh": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + } + } + }, + "gpt-5.4-mini": { + "name": "GPT 5.4 Mini (OA-chat)", + "limit": { + "context": 400000, + "output": 128000 + }, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "variants": { + "none": { + "reasoningEffort": "none", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "low": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "medium": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "high": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + }, + "xhigh": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + } + } + }, + "gpt-5.4-nano": { + "name": "GPT 5.4 Nano (OA-chat)", + "limit": { + "context": 400000, + "output": 128000 + }, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "variants": { + "none": { + "reasoningEffort": "none", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "low": { + "reasoningEffort": "low", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "medium": { + "reasoningEffort": "medium", + "reasoningSummary": "auto", + "textVerbosity": "medium" + }, + "high": { + "reasoningEffort": "high", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + }, + "xhigh": { + "reasoningEffort": "xhigh", + "reasoningSummary": "detailed", + "textVerbosity": "medium" + } + } + }, "gpt-5.2": { "name": "GPT 5.2 (OAuth)", "limit": { diff --git a/lib/oauth-success.html b/lib/oauth-success.html index 9ab0252..d11c9bd 100644 --- a/lib/oauth-success.html +++ b/lib/oauth-success.html @@ -548,7 +548,7 @@
-
opencode-openai-codex-auth@4.4.0 — OAuth Authentication
+
opencode-openai-codex-auth@4.5.0 — OAuth Authentication
diff --git a/lib/request/helpers/model-map.ts b/lib/request/helpers/model-map.ts index 20ca329..3e8778a 100644 --- a/lib/request/helpers/model-map.ts +++ b/lib/request/helpers/model-map.ts @@ -13,8 +13,66 @@ */ export const MODEL_MAP: Record = { // ============================================================================ -// GPT-5.1 Codex Models +// GPT-5.5 Models (supports none/low/medium/high/xhigh, schema carried over from 5.2) // ============================================================================ + "gpt-5.5": "gpt-5.5", + "gpt-5.5-none": "gpt-5.5", + "gpt-5.5-low": "gpt-5.5", + "gpt-5.5-medium": "gpt-5.5", + "gpt-5.5-high": "gpt-5.5", + "gpt-5.5-xhigh": "gpt-5.5", + + // ============================================================================ + // GPT-5.5 Codex Models (low/medium/high/xhigh) + // ============================================================================ + "gpt-5.5-codex": "gpt-5.5-codex", + "gpt-5.5-codex-low": "gpt-5.5-codex", + "gpt-5.5-codex-medium": "gpt-5.5-codex", + "gpt-5.5-codex-high": "gpt-5.5-codex", + "gpt-5.5-codex-xhigh": "gpt-5.5-codex", + + // ============================================================================ + // GPT-5.4 Models (supports none/low/medium/high/xhigh, schema carried over from 5.2) + // ============================================================================ + "gpt-5.4": "gpt-5.4", + "gpt-5.4-none": "gpt-5.4", + "gpt-5.4-low": "gpt-5.4", + "gpt-5.4-medium": "gpt-5.4", + "gpt-5.4-high": "gpt-5.4", + "gpt-5.4-xhigh": "gpt-5.4", + + // ============================================================================ + // GPT-5.4 Codex Models (low/medium/high/xhigh) + // ============================================================================ + "gpt-5.4-codex": "gpt-5.4-codex", + "gpt-5.4-codex-low": "gpt-5.4-codex", + "gpt-5.4-codex-medium": "gpt-5.4-codex", + "gpt-5.4-codex-high": "gpt-5.4-codex", + "gpt-5.4-codex-xhigh": "gpt-5.4-codex", + + // ============================================================================ + // GPT-5.4 Mini Models (400k context; same reasoning schema as gpt-5.4) + // ============================================================================ + "gpt-5.4-mini": "gpt-5.4-mini", + "gpt-5.4-mini-none": "gpt-5.4-mini", + "gpt-5.4-mini-low": "gpt-5.4-mini", + "gpt-5.4-mini-medium": "gpt-5.4-mini", + "gpt-5.4-mini-high": "gpt-5.4-mini", + "gpt-5.4-mini-xhigh": "gpt-5.4-mini", + + // ============================================================================ + // GPT-5.4 Nano Models (400k context; same reasoning schema as gpt-5.4) + // ============================================================================ + "gpt-5.4-nano": "gpt-5.4-nano", + "gpt-5.4-nano-none": "gpt-5.4-nano", + "gpt-5.4-nano-low": "gpt-5.4-nano", + "gpt-5.4-nano-medium": "gpt-5.4-nano", + "gpt-5.4-nano-high": "gpt-5.4-nano", + "gpt-5.4-nano-xhigh": "gpt-5.4-nano", + + // ============================================================================ + // GPT-5.1 Codex Models + // ============================================================================ "gpt-5.1-codex": "gpt-5.1-codex", "gpt-5.1-codex-low": "gpt-5.1-codex", "gpt-5.1-codex-medium": "gpt-5.1-codex", @@ -120,3 +178,31 @@ export function getNormalizedModel(modelId: string): string | undefined { export function isKnownModel(modelId: string): boolean { return getNormalizedModel(modelId) !== undefined; } + +/** + * Wire model overrides + * + * Some normalized model names are our own internal "family key" - used to pick + * system instructions and reasoning-effort defaults - but are NOT valid model + * names on the ChatGPT/Codex backend itself. Confirmed via testing: unlike + * GPT-5.1/5.2, GPT-5.4/5.5 do not have a distinct "-codex" backend deployment - + * the ChatGPT backend rejects "gpt-5.4-codex"/"gpt-5.5-codex" outright + * ("model is not supported when using Codex with a ChatGPT account"). The + * "codex" flavor for these is purely a prompt/reasoning-defaults choice on our + * side, so the literal API call must use the plain model name. + */ +const WIRE_MODEL_OVERRIDES: Record = { + "gpt-5.4-codex": "gpt-5.4", + "gpt-5.5-codex": "gpt-5.5", +}; + +/** + * Resolve the literal model name to send to the ChatGPT/Codex backend. + * Defaults to the normalized model name unchanged unless a wire override applies. + * + * @param normalizedModel - Our internal normalized/family-key model name + * @returns The model string to actually put on the wire + */ +export function resolveWireModel(normalizedModel: string): string { + return WIRE_MODEL_OVERRIDES[normalizedModel] ?? normalizedModel; +} diff --git a/lib/request/request-transformer.ts b/lib/request/request-transformer.ts index 07f9961..53352ad 100644 --- a/lib/request/request-transformer.ts +++ b/lib/request/request-transformer.ts @@ -2,7 +2,7 @@ import { logDebug, logWarn } from "../logger.js"; import { TOOL_REMAP_MESSAGE } from "../prompts/codex.js"; import { CODEX_OPENCODE_BRIDGE } from "../prompts/codex-opencode-bridge.js"; import { getOpenCodeCodexPrompt } from "../prompts/opencode-codex.js"; -import { getNormalizedModel } from "./helpers/model-map.js"; +import { getNormalizedModel, resolveWireModel } from "./helpers/model-map.js"; import { filterOpenCodeSystemPromptsWithCachedPrompt, normalizeOrphanedToolOutputs, @@ -47,6 +47,36 @@ export function normalizeModel(model: string | undefined): string { const normalized = modelId.toLowerCase(); // Priority order for pattern matching (most specific first): + // 0a. GPT-5.5 Codex / GPT-5.4 Codex (newest codex models) + if ( + normalized.includes("gpt-5.5-codex") || + normalized.includes("gpt 5.5 codex") + ) { + return "gpt-5.5-codex"; + } + if ( + normalized.includes("gpt-5.4-codex") || + normalized.includes("gpt 5.4 codex") + ) { + return "gpt-5.4-codex"; + } + + // 0b. GPT-5.4 Mini / Nano (distinct context limits from base gpt-5.4) + if (normalized.includes("gpt-5.4-mini") || normalized.includes("gpt 5.4 mini")) { + return "gpt-5.4-mini"; + } + if (normalized.includes("gpt-5.4-nano") || normalized.includes("gpt 5.4 nano")) { + return "gpt-5.4-nano"; + } + + // 0c. GPT-5.5 / GPT-5.4 (general purpose) + if (normalized.includes("gpt-5.5") || normalized.includes("gpt 5.5")) { + return "gpt-5.5"; + } + if (normalized.includes("gpt-5.4") || normalized.includes("gpt 5.4")) { + return "gpt-5.4"; + } + // 1. GPT-5.2 Codex (newest codex model) if ( normalized.includes("gpt-5.2-codex") || @@ -195,15 +225,6 @@ export function getReasoningConfig( ): ReasoningConfig { const normalizedName = modelName?.toLowerCase() ?? ""; - // GPT-5.2 Codex is the newest codex model (supports xhigh, but not "none") - const isGpt52Codex = - normalizedName.includes("gpt-5.2-codex") || - normalizedName.includes("gpt 5.2 codex"); - - // GPT-5.2 general purpose (not codex variant) - const isGpt52General = - (normalizedName.includes("gpt-5.2") || normalizedName.includes("gpt 5.2")) && - !isGpt52Codex; const isCodexMax = normalizedName.includes("codex-max") || normalizedName.includes("codex max"); @@ -212,39 +233,41 @@ export function getReasoningConfig( normalizedName.includes("codex mini") || normalizedName.includes("codex_mini") || normalizedName.includes("codex-mini-latest"); - const isCodex = normalizedName.includes("codex") && !isCodexMini; + const isCodex = + normalizedName.includes("codex") && !isCodexMini && !isCodexMax; + const isGeneralPurpose = !isCodex && !isCodexMax && !isCodexMini; const isLightweight = !isCodexMini && (normalizedName.includes("nano") || normalizedName.includes("mini")); - // GPT-5.1 general purpose (not codex variants) - supports "none" per OpenAI API docs - const isGpt51General = - (normalizedName.includes("gpt-5.1") || normalizedName.includes("gpt 5.1")) && - !isCodex && - !isCodexMax && - !isCodexMini; + // GPT-5.x point release (e.g. "gpt-5.2" -> 2, "gpt-5.4" -> 4). Bare "gpt-5" + // (no minor version) is always remapped to "gpt-5.1" by MODEL_MAP before + // reaching this function, so it's treated like 5.1 here as a safe default. + const versionMatch = normalizedName.match(/gpt[ -]?5\.(\d+)/); + const minorVersion = versionMatch ? parseInt(versionMatch[1], 10) : 1; - // GPT 5.2, GPT 5.2 Codex, and Codex Max support xhigh reasoning - const supportsXhigh = isGpt52General || isGpt52Codex || isCodexMax; + // xhigh reasoning was introduced with the 5.2 generation (GPT-5.2, GPT-5.2 + // Codex) and the same schema has carried forward to every later point + // release (5.4, 5.5, ...); Codex Max supports it regardless of version. + const supportsXhigh = + isCodexMax || (minorVersion >= 2 && (isCodex || isGeneralPurpose)); - // GPT 5.1 general and GPT 5.2 general support "none" reasoning per: - // - OpenAI API docs: "gpt-5.1 defaults to none, supports: none, low, medium, high" - // - Codex CLI: ReasoningEffort enum includes None variant (codex-rs/protocol/src/openai_models.rs) - // - Codex CLI: docs/config.md lists "none" as valid for model_reasoning_effort - // - gpt-5.2 (being newer) also supports: none, low, medium, high, xhigh - // - Codex models (including GPT-5.2 Codex) do NOT support "none" - const supportsNone = isGpt52General || isGpt51General; + // "none" reasoning is only offered on general-purpose models per OpenAI API + // docs (gpt-5.1 and gpt-5.2 both support it, and later point releases + // carry the same schema forward). Codex-tuned models (including Codex + // Max/Mini) never support it. + const supportsNone = isGeneralPurpose; // Default based on model type (Codex CLI defaults) // Note: OpenAI docs say gpt-5.1 defaults to "none", but we default to "medium" // for better coding assistance unless user explicitly requests "none" const defaultEffort: ReasoningConfig["effort"] = isCodexMini ? "medium" - : supportsXhigh - ? "high" - : isLightweight - ? "minimal" + : isLightweight + ? "minimal" + : supportsXhigh + ? "high" : "medium"; // Get user-requested effort @@ -442,8 +465,11 @@ export async function transformRequestBody( }, ); - // Normalize model name for API call - body.model = normalizedModel; + // Normalize model name for API call. Note: this may differ from `normalizedModel` + // itself - some normalized names are internal-only "family keys" used for + // reasoning/instructions selection but aren't valid literal model names on the + // ChatGPT backend (e.g. "gpt-5.4-codex" -> "gpt-5.4"). See resolveWireModel. + body.model = resolveWireModel(normalizedModel); // Codex required fields // ChatGPT backend REQUIRES store=false (confirmed via testing) diff --git a/package.json b/package.json index caf949c..703363d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opencode-openai-codex-auth", - "version": "4.4.0", + "version": "4.5.0", "description": "OpenAI ChatGPT (Codex backend) OAuth auth plugin for opencode - use your ChatGPT Plus/Pro subscription instead of API credits", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/test/request-transformer.test.ts b/test/request-transformer.test.ts index b3c0404..528d9f1 100644 --- a/test/request-transformer.test.ts +++ b/test/request-transformer.test.ts @@ -104,6 +104,42 @@ describe('Request Transformer Module', () => { expect(normalizeModel('openai/gpt-5.1')).toBe('gpt-5.1'); expect(normalizeModel('GPT 5.1 High')).toBe('gpt-5.1'); }); + + it('should normalize gpt-5.4 presets', async () => { + expect(normalizeModel('gpt-5.4')).toBe('gpt-5.4'); + expect(normalizeModel('gpt-5.4-none')).toBe('gpt-5.4'); + expect(normalizeModel('gpt-5.4-high')).toBe('gpt-5.4'); + expect(normalizeModel('gpt-5.4-xhigh')).toBe('gpt-5.4'); + expect(normalizeModel('openai/gpt-5.4-medium')).toBe('gpt-5.4'); + }); + + it('should normalize gpt-5.4-codex presets', async () => { + expect(normalizeModel('gpt-5.4-codex')).toBe('gpt-5.4-codex'); + expect(normalizeModel('gpt-5.4-codex-low')).toBe('gpt-5.4-codex'); + expect(normalizeModel('gpt-5.4-codex-xhigh')).toBe('gpt-5.4-codex'); + expect(normalizeModel('openai/gpt-5.4-codex-high')).toBe('gpt-5.4-codex'); + }); + + it('should normalize gpt-5.4-mini and gpt-5.4-nano presets distinctly from gpt-5.4', async () => { + expect(normalizeModel('gpt-5.4-mini')).toBe('gpt-5.4-mini'); + expect(normalizeModel('gpt-5.4-mini-high')).toBe('gpt-5.4-mini'); + expect(normalizeModel('gpt-5.4-nano')).toBe('gpt-5.4-nano'); + expect(normalizeModel('gpt-5.4-nano-low')).toBe('gpt-5.4-nano'); + }); + + it('should normalize gpt-5.5 and gpt-5.5-codex presets', async () => { + expect(normalizeModel('gpt-5.5')).toBe('gpt-5.5'); + expect(normalizeModel('gpt-5.5-xhigh')).toBe('gpt-5.5'); + expect(normalizeModel('gpt-5.5-codex')).toBe('gpt-5.5-codex'); + expect(normalizeModel('gpt-5.5-codex-high')).toBe('gpt-5.5-codex'); + expect(normalizeModel('openai/gpt-5.5-codex-xhigh')).toBe('gpt-5.5-codex'); + }); + + it('should fall back correctly for unmapped gpt-5.4/5.5 variants', async () => { + // Not in MODEL_MAP, but should still pattern-match to the right family + expect(normalizeModel('gpt-5.4-super-high')).toBe('gpt-5.4'); + expect(normalizeModel('gpt-5.5-codex-super-high')).toBe('gpt-5.5-codex'); + }); }); // Edge case tests - legacy gpt-5 models now map to gpt-5.1 @@ -1070,6 +1106,74 @@ describe('Request Transformer Module', () => { expect(result.reasoning?.effort).toBe('low'); }); + describe('GPT-5.4 / GPT-5.5 (schema carried forward from GPT-5.2)', () => { + it('should default gpt-5.4 and gpt-5.5 general purpose to high effort (supports xhigh)', async () => { + const gpt54 = await transformRequestBody({ model: 'gpt-5.4', input: [] }, codexInstructions); + expect(gpt54.model).toBe('gpt-5.4'); + expect(gpt54.reasoning?.effort).toBe('high'); + + const gpt55 = await transformRequestBody({ model: 'gpt-5.5', input: [] }, codexInstructions); + expect(gpt55.model).toBe('gpt-5.5'); + expect(gpt55.reasoning?.effort).toBe('high'); + }); + + it('should default gpt-5.4-codex and gpt-5.5-codex to high effort (supports xhigh)', async () => { + const result = await transformRequestBody({ model: 'gpt-5.4-codex', input: [] }, codexInstructions); + expect(result.reasoning?.effort).toBe('high'); + + const result2 = await transformRequestBody({ model: 'gpt-5.5-codex', input: [] }, codexInstructions); + expect(result2.reasoning?.effort).toBe('high'); + }); + + it('should send the plain model name on the wire for gpt-5.4-codex/gpt-5.5-codex (confirmed: backend rejects the -codex suffix for these)', async () => { + // Unlike GPT-5.1/5.2, GPT-5.4/5.5 have no distinct "-codex" backend + // deployment - "codex" here is only a prompt/reasoning-defaults choice + // on our side. Sending the literal "-codex" suffix as the model name + // gets rejected by the ChatGPT backend with a 400 Bad Request. + const result = await transformRequestBody({ model: 'gpt-5.4-codex', input: [] }, codexInstructions); + expect(result.model).toBe('gpt-5.4'); + + const result2 = await transformRequestBody({ model: 'gpt-5.5-codex-high', input: [] }, codexInstructions); + expect(result2.model).toBe('gpt-5.5'); + }); + + it('should preserve xhigh for gpt-5.4/5.5 general purpose and codex', async () => { + const userConfig: UserConfig = { global: { reasoningEffort: 'xhigh' }, models: {} }; + + const general = await transformRequestBody({ model: 'gpt-5.4-xhigh', input: [] }, codexInstructions, userConfig); + expect(general.reasoning?.effort).toBe('xhigh'); + + const codex = await transformRequestBody({ model: 'gpt-5.5-codex-xhigh', input: [] }, codexInstructions, userConfig); + expect(codex.reasoning?.effort).toBe('xhigh'); + }); + + it('should preserve none for gpt-5.4/5.5 general purpose but upgrade to low for codex', async () => { + const userConfig: UserConfig = { global: { reasoningEffort: 'none' }, models: {} }; + + const general = await transformRequestBody({ model: 'gpt-5.5-none', input: [] }, codexInstructions, userConfig); + expect(general.reasoning?.effort).toBe('none'); + + const codex = await transformRequestBody({ model: 'gpt-5.4-codex', input: [] }, codexInstructions, userConfig); + expect(codex.reasoning?.effort).toBe('low'); + }); + + it('should default gpt-5.4-mini/nano to a low effort despite supporting xhigh', async () => { + const mini = await transformRequestBody({ model: 'gpt-5.4-mini', input: [] }, codexInstructions); + expect(mini.model).toBe('gpt-5.4-mini'); + expect(mini.reasoning?.effort).toBe('low'); + + const nano = await transformRequestBody({ model: 'gpt-5.4-nano', input: [] }, codexInstructions); + expect(nano.model).toBe('gpt-5.4-nano'); + expect(nano.reasoning?.effort).toBe('low'); + }); + + it('should still allow explicit xhigh/none overrides for gpt-5.4-mini/nano', async () => { + const userConfig: UserConfig = { global: { reasoningEffort: 'xhigh' }, models: {} }; + const mini = await transformRequestBody({ model: 'gpt-5.4-mini', input: [] }, codexInstructions, userConfig); + expect(mini.reasoning?.effort).toBe('xhigh'); + }); + }); + it('should use minimal effort for lightweight models', async () => { const body: RequestBody = { model: 'gpt-5-nano',