# models


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## The catalog

`CATALOG` maps each provider to the model ids a picker offers before
anything is asked over the network, in display order.

- `claude` is what Claude Code’s `--model` accepts: the tier names
  `fable`, `opus`, `sonnet` and `haiku`, which follow the newest model
  of each family, then the full ids.
- `anthropic` is the same models through the Anthropic API.
- `openai` and `gemini` are the models offered through those APIs.
- `codex` is what Codex’s own picker lists.

[`claude_ids`](https://vedicreader.github.io/ramabana/models.html#claude_ids)
returns the full Claude ids without the tier names.
[`claude_aliases`](https://vedicreader.github.io/ramabana/models.html#claude_aliases)
maps each tier name to the id it means here, which is the last id of
that family in `CATALOG`. Adding a newer Opus to the end of the list
moves `opus` to it.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ramabana/blob/main/ramabana/models.py#L27"
target="_blank" style="float:right; font-size:smaller">source</a>

### claude_aliases

``` python
def claude_aliases():
```

*Each Claude Code tier alias and the concrete id it tracks here: the
family’s last, so newest, in `CATALOG`.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ramabana/blob/main/ramabana/models.py#L23"
target="_blank" style="float:right; font-size:smaller">source</a>

### claude_ids

``` python
def claude_ids():
```

*The concrete Claude Code ids in `CATALOG`, without the tier aliases.*

``` python
test_eq(claude_aliases()['fable'], 'claude-fable-5-1')
test_eq(claude_aliases()['opus'], 'claude-opus-5-5')
assert 'claude-haiku-5-5' in claude_ids() and 'claude-haiku-5-5' in CATALOG['anthropic']
test_eq(claude_aliases()['haiku'], 'claude-haiku-5-5')
assert 'claude-sonnet-5-5' in claude_ids() and claude_aliases()['sonnet'] == 'claude-sonnet-5-5'
assert 'gpt-6.1-sol' in CATALOG['openai'] and CATALOG['openai'][0] == 'gpt-6-astra'
test_eq(set(claude_aliases()), {'fable', 'opus', 'sonnet', 'haiku'})
for p, ids in CATALOG.items(): test_eq(len(ids), len(set(ids))), p
```

## Live discovery

Each provider that can be asked for its models has one function in
`LIVE`. The function returns the ids the provider lists, or `[]` when it
cannot ask: no key, no file, no network, or an answer it cannot read. It
never raises.

- [`openai_ids`](https://vedicreader.github.io/ramabana/models.html#openai_ids)
  asks OpenAI’s `/v1/models` with `$OPENAI_API_KEY`.
- [`anthropic_ids`](https://vedicreader.github.io/ramabana/models.html#anthropic_ids)
  asks Anthropic’s `/v1/models` with `$ANTHROPIC_API_KEY`. It serves
  both `anthropic` and `claude`, since Claude Code runs the same models.
- [`codex_ids`](https://vedicreader.github.io/ramabana/models.html#codex_ids)
  asks no server. Codex keeps its picker list in `models_cache.json`
  under `$CODEX_HOME`, which defaults to `~/.codex`, and the entries
  marked `visibility: 'list'` are the ones it offers. The test below
  writes a small cache file and reads it back.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ramabana/blob/main/ramabana/models.py#L51"
target="_blank" style="float:right; font-size:smaller">source</a>

### codex_ids

``` python
def codex_ids():
```

*What Codex’s own picker lists, from its models cache, or `[]`.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ramabana/blob/main/ramabana/models.py#L46"
target="_blank" style="float:right; font-size:smaller">source</a>

### anthropic_ids

``` python
def anthropic_ids():
```

*Every id the current Anthropic key can list, or `[]` when it cannot be
asked.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ramabana/blob/main/ramabana/models.py#L41"
target="_blank" style="float:right; font-size:smaller">source</a>

### openai_ids

``` python
def openai_ids():
```

*Every id the current OpenAI key can list, or `[]` when it cannot be
asked.*

``` python
import json, os, tempfile
with tempfile.TemporaryDirectory() as d:
    cache = {'models': [{'slug': 'gpt-6-astra', 'visibility': 'list'}, {'slug': 'gpt-reserve', 'visibility': 'hide'}, {'slug': 'codex-auto-review', 'visibility': 'hide'}]}
    (Path(d)/'models_cache.json').write_text(json.dumps(cache))
    _old = os.environ.get('CODEX_HOME')
    os.environ['CODEX_HOME'] = d
    try: test_eq(codex_ids(), ['gpt-6-astra'])
    finally:
        if _old is None: del os.environ['CODEX_HOME']
        else: os.environ['CODEX_HOME'] = _old
    os.environ['CODEX_HOME'] = str(Path(d)/'nowhere')
    try: test_eq(codex_ids(), [])
    finally:
        if _old is None: del os.environ['CODEX_HOME']
        else: os.environ['CODEX_HOME'] = _old
```

## What a live listing may add

A provider’s listing holds everything it serves: dated snapshots,
fine-tunes, realtime and audio models, embeddings, moderation, image and
search models. A picker wants none of these.
[`_current`](https://vedicreader.github.io/ramabana/models.html#_current)
decides which listed ids a picker may show. It hides an id by what kind
of model it is, not by which generation it belongs to, so a new
generation shows up the day the provider lists it. Older chat
generations (`gpt-4*`, `gpt-3.5*`, `claude-2-*` and `claude-3-*`) appear
only with `legacy=True`. The cell below lists ids it keeps, ids it
always hides, and ids it shows only with `legacy`.

``` python
for ok in ('gpt-6-astra', 'gpt-7', 'o5-mini', 'claude-fable-5-1', 'claude-haiku-4-5-20251001', 'gemini-3.5-flash'): assert _current('openai', ok), ok
for hidden in ('gpt-6-astra-2026-09-01', 'gpt-3.5-turbo-0125', 'ft:gpt-3.5-turbo-0125:org::abc', 'ada:ft-org-2023-04-06-20-44-19', 'gpt-realtime', 'gpt-6-audio', 'gpt-image-2', 'gpt-6-transcribe', 'tts-1', 'gpt-live-1', 'gpt-3.5-turbo-instruct', 'text-embedding-4', 'omni-moderation-latest', 'dall-e-3', 'whisper-1', 'davinci-002', 'babbage-002', 'sora-2', 'computer-use-preview', 'gpt-5-search-api', 'gpt-4o-search-preview', 'o3-deep-research', 'chat-latest', ''):
    assert not _current('openai', hidden), hidden
    assert not _current('openai', hidden, legacy=True), hidden
for old in ('gpt-4.1', 'gpt-4o', 'gpt-3.5-turbo', 'claude-3-5-haiku-latest'):
    assert not _current('openai', old) and _current('openai', old, legacy=True), old
```

## What a picker offers

`provider_models(p)` is the list a picker shows for provider `p`. It
starts with the `CATALOG` ids in their order, then adds the ids from the
live listing that
[`_current`](https://vedicreader.github.io/ramabana/models.html#_current)
admits and that are not dated snapshots, newest first by version number,
with nothing repeated. The live answer is remembered for five minutes
with
[`ramabana.core.probed`](https://vedicreader.github.io/ramabana/core.html#probed).
If the listing fails, the result is the catalog alone. A provider that
is neither in `CATALOG` nor in `LIVE` gives `[]`.

The first cell below stands in a fake OpenAI listing. The second makes
every listing fail. The third fakes Anthropic’s endpoint and shows that
`claude` and `anthropic` share one request.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ramabana/blob/main/ramabana/models.py#L68"
target="_blank" style="float:right; font-size:smaller">source</a>

### provider_models

``` python
def provider_models(
    p, legacy:bool=False, ttl:int=300
):
```

*Ids a picker offers for provider `p`: the curated ones in their order,
then what live discovery adds.*

``` python
from ramabana.core import forget_probes
_orig = dict(LIVE)
forget_probes()
LIVE['openai'] = lambda: ['gpt-6-astra', 'gpt-6-nova', 'gpt-6-nova-2026-09-01', 'gpt-6-audio', 'gpt-4.1']
try:
    got = provider_models('openai')
    test_eq(got[:len(CATALOG['openai'])], list(CATALOG['openai']))
    test_eq(got[len(CATALOG['openai']):], ['gpt-6-nova'])
    test_eq(got.count('gpt-6-astra'), 1)
    assert 'gpt-4.1' in provider_models('openai', legacy=True)
    # newest first, by the numbers: alphabetical put gpt-6.1 after every gpt-5
    forget_probes(); LIVE['openai'] = lambda: ['gpt-5.1', 'gpt-6.1-nova', 'gpt-5.10']
    test_eq(provider_models('openai')[len(CATALOG['openai']):], ['gpt-6.1-nova', 'gpt-5.10', 'gpt-5.1'])
finally: LIVE.clear(); LIVE.update(_orig); forget_probes()
```

``` python
def _boom(): raise OSError('no network')
forget_probes()
LIVE.update({p: _boom for p in LIVE})
try:
    for p in CATALOG: test_eq(provider_models(p)[:len(CATALOG[p])], list(CATALOG[p]))
finally: LIVE.clear(); LIVE.update(_orig); forget_probes()
test_eq(provider_models('nobody'), [])
```

``` python
_n, _key, _lst = [], os.environ.get('ANTHROPIC_API_KEY'), _listed
def _fake(*a, **kw): _n.append(1); return ['claude-sonnet-6', 'claude-sonnet-6-20270101', 'claude-sonnet-5-5']
forget_probes(); os.environ['ANTHROPIC_API_KEY'] = 'k'; _listed = _fake
try:
    got = provider_models('claude')
    test_eq(got[:len(CATALOG['claude'])], list(CATALOG['claude']))
    test_eq(got[len(CATALOG['claude']):], ['claude-sonnet-6'])
    assert 'claude-sonnet-6' in provider_models('anthropic') and len(_n) == 1
    forget_probes(); del os.environ['ANTHROPIC_API_KEY']
    test_eq(provider_models('claude'), list(CATALOG['claude']))
finally:
    _listed = _lst; forget_probes()
    if _key is None: os.environ.pop('ANTHROPIC_API_KEY', None)
    else: os.environ['ANTHROPIC_API_KEY'] = _key
```

## Through [`available_models`](https://vedicreader.github.io/ramabana/core.html#available_models)

[`ramabana.core.available_models`](https://vedicreader.github.io/ramabana/core.html#available_models)
builds its hosted rows from
[`provider_models`](https://vedicreader.github.io/ramabana/models.html#provider_models),
and includes a provider only when
[`runtime_available`](https://vedicreader.github.io/ramabana/core.html#runtime_available)
and
[`auth_status`](https://vedicreader.github.io/ramabana/core.html#auth_status)
say it can be used here. Each row has a `value`, `label`, `provider` and
`source`. With a fake OpenAI key and listing, the OpenAI rows are the
catalog followed by the one new model:

``` python
import ramabana.models as _m
from ramabana.core import available_models, CLAUDE, CLAUDE_ALIASES, CLAUDE_MODELS
_orig_live, _key = dict(_m.LIVE), os.environ.get('OPENAI_API_KEY')
forget_probes()
_m.LIVE.update({'openai': lambda: ['gpt-6-astra', 'gpt-6-nova', 'gpt-6-nova-2026-09-01', 'gpt-6-audio'], 'anthropic': lambda: [], 'codex': lambda: []})
os.environ['OPENAI_API_KEY'] = 'sk-not-a-real-key'
try:
    rows = available_models()
    test_eq({tuple(sorted(r)) for r in rows}, {('label', 'provider', 'source', 'value')})
    openai = [r['label'] for r in rows if r['provider'] == 'openai']
    test_eq(openai, [*CATALOG['openai'], 'gpt-6-nova'])
    test_eq([r['value'] for r in rows if r['provider'] == 'openai'][0], 'openai/gpt-6-astra')
finally:
    _m.LIVE.clear(); _m.LIVE.update(_orig_live); forget_probes()
    if _key is None: del os.environ['OPENAI_API_KEY']
    else: os.environ['OPENAI_API_KEY'] = _key
test_eq(CLAUDE_MODELS, claude_ids())
assert {'claude-fable-5-1', 'claude-opus-5-5'} <= set(CLAUDE_MODELS)
test_eq(CLAUDE_ALIASES['fable'], 'claude-fable-5-1')
test_eq(CLAUDE['claude/opus'], 'claude-opus-5-5')
```
