Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

[claude-code-router]: https://ccrdesk.top/en/configuration/fusion-models/

### Added

- **GPT-5.6 (Sol / Terra / Luna).** OpenAI's current frontier generation is
three durable capability tiers on one generation rather than a size ladder:
Sol is the flagship, Terra balances capability against cost, Luna is the
cheap high-volume tier, and `gpt-5.6` is OpenAI's alias for Sol. All four are
offered by the direct OpenAI provider and by OpenRouter (which also carries
`-pro` variants of each tier).

Each carries a real `ModelConfig` (1.05M context / 128K output). Without one
they resolved through the prefix fallback to the 272K catch-all, which fires
auto-compact roughly three quarters of a window early. The bare `gpt-5.6`
alias is deliberately registered *after* `gpt-5.5`: its prefix base is `gpt`,
so listing it first would make it the catch-all for every unknown `gpt` id
and hand them a 1.05M window — over-estimating overflows the context, while
under-estimating only compacts early.

Not changed: `default_model` stays `gpt-5.4`, matching how Anthropic defaults
to `claude-sonnet-4-6` while listing `claude-fable-5` first — the default is a
cost-sensible tier, not the frontier. The ChatGPT-subscription allow-list
(`SUBSCRIPTION_MODELS`) is also untouched, since which models that backend
serves is a wire fact that has to be observed rather than assumed.

### Fixed

- **OpenRouter's curated model list offered ids OpenRouter had delisted.** The
OpenAI section still led with `openai/gpt-5` / `openai/gpt-4o` / `openai/o1`
while the gateway had moved on to the `gpt-5.6` family, and
`openai/o1-mini` had been removed upstream entirely — so the /model picker
offered a row that fails at request time, the same "offers something you
cannot select" shape as the picker bug above. Refreshed against
`https://openrouter.ai/api/v1/models`: the OpenAI block now leads with
`gpt-5.6-luna/terra/sol` (and their `-pro` tiers), keeps the codex line and
the o-series, and drops the delisted ids. Batch (`:batch`), image, audio,
and embedding variants are deliberately excluded — none of them serves an
interactive agent turn.

Validating the rest of the list caught five more dead ids:
`anthropic/claude-3.5-sonnet`, `anthropic/claude-3.5-haiku`,
`google/gemini-2.0-flash`, `meta-llama/llama-3.1-405b-instruct`,
`deepseek/deepseek-v3.2-speciale`, and `x-ai/grok-2`. All are replaced with
live successors; every id in the catalogue now resolves.

The list had also been duplicated verbatim between
`PROVIDER_INFO["openrouter"]` (which feeds `login` and the picker) and
`OpenRouterProvider._curated_models()` (which feeds discovery's curation).
Two lists for one set drift the moment someone edits one of them, and the
drift reads as a model one surface offers and the other drops, so the
provider now reads the registry.

- **`/model` listed one provider instead of every configured one.** Step 1 of
the picker showed a single row — `anthropic · 22 models` — no matter how many
providers were set up. `model.options` was a stub: it called the
Expand Down
38 changes: 38 additions & 0 deletions src/models/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,12 +390,50 @@ class ModelConfig:
# entries so they never take that path. As with the Meta entry above,
# max_output_tokens is not sent on the wire for OpenAI providers — its
# live effect is the auto-compact output reservation (clamped at 20K).
# GPT-5.6 (Sol / Terra / Luna — three durable capability tiers on one
# generation, 1.05M context each). These keys sit BEFORE "gpt-5.5"
# deliberately: ``get_model_config``'s prefix fallback walks in insertion
# order and each of these has base "gpt-5.6" (rsplit on the last "-"), so
# putting them first is what lets an unlisted variant like
# ``gpt-5.6-sol-pro`` resolve to 1.05M instead of falling through to the
# 272K catch-all. The bare ``gpt-5.6`` alias is deliberately NOT here —
# see below.
"gpt-5.6-sol": ModelConfig(
model_id="gpt-5.6-sol",
display_name="GPT-5.6 Sol",
context_window=1_048_576,
max_output_tokens=128_000,
),
"gpt-5.6-terra": ModelConfig(
model_id="gpt-5.6-terra",
display_name="GPT-5.6 Terra",
context_window=1_048_576,
max_output_tokens=128_000,
),
"gpt-5.6-luna": ModelConfig(
model_id="gpt-5.6-luna",
display_name="GPT-5.6 Luna",
context_window=1_048_576,
max_output_tokens=128_000,
),
"gpt-5.5": ModelConfig(
model_id="gpt-5.5",
display_name="GPT-5.5",
context_window=272_000,
max_output_tokens=128_000,
),
# ``gpt-5.6`` is OpenAI's alias for Sol. Its base is "gpt" (not
# "gpt-5.6"), so it would become the catch-all for EVERY unknown gpt id if
# it preceded "gpt-5.5" — handing them a 1.05M window. Over-estimating
# overflows the context; under-estimating only compacts early, so the
# catch-all must stay on the 272K entry. Exact lookups are unaffected by
# position: ``get_model_config`` checks for an exact key first.
"gpt-5.6": ModelConfig(
model_id="gpt-5.6",
display_name="GPT-5.6 (Sol)",
context_window=1_048_576,
max_output_tokens=128_000,
),
"gpt-5.4": ModelConfig(
model_id="gpt-5.4",
display_name="GPT-5.4",
Expand Down
59 changes: 49 additions & 10 deletions src/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ class ProviderInfo(TypedDict):
"default_base_url": "https://api.openai.com/v1",
"default_model": "gpt-5.4",
"available_models": [
# GPT-5.5 (flagship; also served by the ChatGPT subscription)
# GPT-5.6 — the current frontier generation. Sol / Terra / Luna are
# durable capability tiers rather than a size ladder: Sol is the
# flagship, Terra balances capability against cost, Luna is the
# cheap high-volume tier. ``gpt-5.6`` is OpenAI's alias for Sol.
"gpt-5.6",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
# GPT-5.5 (also served by the ChatGPT subscription)
"gpt-5.5",
# GPT-5.4 series
"gpt-5.4",
Expand Down Expand Up @@ -137,36 +145,67 @@ class ProviderInfo(TypedDict):
"deepseek/deepseek-v4-pro",
"deepseek/deepseek-v4-flash",
# Anthropic
"anthropic/claude-fable-5",
"anthropic/claude-sonnet-4.5",
"anthropic/claude-opus-4.1",
"anthropic/claude-opus-4",
"anthropic/claude-haiku-4.5",
"anthropic/claude-3.5-sonnet",
"anthropic/claude-3.5-haiku",
# OpenAI
# OpenAI — frontier first. OpenRouter carries ~70 openai/* ids;
# this is the coding-relevant slice. The `:batch` variants (the
# asynchronous Batch API), image/audio/embedding models, and the
# `-chat` tunings are deliberately absent — none of them serves an
# interactive agent turn. Any id OpenRouter lists still works:
# free-text model ids are accepted regardless of this list.
# Sol / Terra / Luna are capability tiers, not a size ladder:
# Sol leads, Terra balances cost, Luna is the cheap high-volume
# tier. Ordered flagship-first to match the rest of the list.
"openai/gpt-5.6-sol-pro",
"openai/gpt-5.6-sol",
"openai/gpt-5.6-terra-pro",
"openai/gpt-5.6-terra",
"openai/gpt-5.6-luna-pro",
"openai/gpt-5.6-luna",
"openai/gpt-5.5-pro",
"openai/gpt-5.5",
"openai/gpt-5.4-pro",
"openai/gpt-5.4",
"openai/gpt-5.4-mini",
"openai/gpt-5.4-nano",
# Coding-specialised
"openai/gpt-5.3-codex",
"openai/gpt-5.2-codex",
"openai/gpt-5.1-codex-max",
# Reasoning (o-series)
"openai/o3-pro",
"openai/o3",
"openai/o4-mini-high",
"openai/o4-mini",
# Previous generation
"openai/gpt-5-pro",
"openai/gpt-5",
"openai/gpt-5-mini",
"openai/gpt-4.1",
"openai/gpt-4o",
"openai/gpt-4o-mini",
"openai/o1",
"openai/o1-mini",
# Google
"google/gemini-2.5-pro",
"google/gemini-2.5-flash",
"google/gemini-2.0-flash",
"google/gemini-2.5-flash-lite",
# Meta
"meta-llama/llama-3.3-70b-instruct",
"meta-llama/llama-3.1-405b-instruct",
"meta-llama/llama-3.1-70b-instruct",
# Mistral
"mistralai/mistral-large",
"mistralai/mixtral-8x22b-instruct",
# DeepSeek (V3.x line — V4 is at top of list)
"deepseek/deepseek-v3.2",
"deepseek/deepseek-v3.2-speciale",
"deepseek/deepseek-v3.2-exp",
"deepseek/deepseek-v3.1-terminus",
"deepseek/deepseek-chat-v3.1",
"deepseek/deepseek-r1-0528",
# xAI
"x-ai/grok-2",
"x-ai/grok-4.5",
"x-ai/grok-4.3",
],
},
}
Expand Down
33 changes: 10 additions & 23 deletions src/providers/openai_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,30 +153,17 @@ def get_available_models(self) -> list[str]:
List of model names
"""
if self._subscription_active:
# A ChatGPT plan serves a SMALLER set than the API key does, so
# this stays its own list — see openai_responses.SUBSCRIPTION_MODELS.
return list(SUBSCRIPTION_MODELS)
return [
# GPT-5.5 (flagship; also the ChatGPT-subscription default family)
"gpt-5.5",
# GPT-5.4 series
"gpt-5.4",
"gpt-5.4-pro",
"gpt-5.4-mini",
"gpt-5.4-nano",
# GPT-5.2 series
"gpt-5.2",
"gpt-5.2-pro",
"gpt-5.2-mini",
"gpt-5.2-nano",
# Codex (coding-specialized)
"gpt-5.3-codex",
"gpt-5.3-codex-spark",
# Legacy GPT-4 series
"gpt-4o",
"gpt-4o-mini",
"gpt-4-turbo",
"gpt-4",
"gpt-3.5-turbo",
]
# Read the registry rather than keep a second copy. This used to
# duplicate PROVIDER_INFO["openai"]["available_models"] verbatim, and
# the halves feed different surfaces — the registry drives `login` and
# the /model picker, this drives discovery — so an update to one shows
# up as a model one offers and the other drops.
from . import PROVIDER_INFO

return list(PROVIDER_INFO["openai"]["available_models"])

# ------------------------------------------------------------------
# ChatGPT-subscription path (Responses API against the Codex backend)
Expand Down
53 changes: 17 additions & 36 deletions src/providers/openrouter_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,39 +86,20 @@ def get_available_models(self) -> list[str]:

@staticmethod
def _curated_models() -> list[str]:
return [
# DeepSeek V4 (latest, strongest — top of the list)
"deepseek/deepseek-v4-pro",
"deepseek/deepseek-v4-flash",
# Anthropic
"anthropic/claude-sonnet-4.5",
"anthropic/claude-opus-4.1",
"anthropic/claude-haiku-4.5",
"anthropic/claude-3.5-sonnet",
"anthropic/claude-3.5-haiku",
# OpenAI
"openai/gpt-5",
"openai/gpt-5-mini",
"openai/gpt-4o",
"openai/gpt-4o-mini",
"openai/o1",
"openai/o1-mini",
# Google
"google/gemini-2.5-pro",
"google/gemini-2.5-flash",
"google/gemini-2.0-flash",
# Meta
"meta-llama/llama-3.3-70b-instruct",
"meta-llama/llama-3.1-405b-instruct",
# Mistral
"mistralai/mistral-large",
"mistralai/mixtral-8x22b-instruct",
# DeepSeek (V3.x line — V4 is at top of list)
"deepseek/deepseek-v3.2",
"deepseek/deepseek-v3.2-speciale",
"deepseek/deepseek-v3.1-terminus",
"deepseek/deepseek-chat-v3.1",
"deepseek/deepseek-r1-0528",
# xAI
"x-ai/grok-2",
]
"""The curated catalogue, read from the one place it is defined.

This used to be a byte-identical second copy of
``PROVIDER_INFO["openrouter"]["available_models"]``. Two lists meant
for the same set drift the moment someone updates one of them, and the
halves are read by different surfaces — the registry entry feeds
``login`` and the /model picker, this feeds discovery's curation — so
the drift shows up as a model the picker offers and discovery drops,
or the reverse.

Imported inside the function, not at module scope: this module is
itself imported lazily by ``get_provider_class``, so a top-level import
of the package that defines that function would be circular.
"""
from . import PROVIDER_INFO

return list(PROVIDER_INFO["openrouter"]["available_models"])
Loading
Loading