# Route and model catalog

The generation-8 catalog separates what earlier designs collapsed into “provider.” A model binding identifies both a model and an executable access route; it does not rely on a hidden default provider.

## Identity model

A route records:

- `api_operator`: who operates the API;
- `access_product`: the subscription, API, gateway, or local service used for access;
- `wire_protocol`: the protocol sent on the wire;
- `billing_mode`: subscription, API, local, or catalog-only;
- `base_url` and `credential_envs` when applicable;
- driver, resource group, transport modalities, and tool behavior.

A model records its `maker`, `route`, and exact `upstream_id` independently. Its catalog key is:

```text
<exact-route-key>__<normalized-upstream-id>
```

Examples:

```text
opencode_go_openai_chat__deepseek_v4_pro
opencode_zen_openai_chat__deepseek_v4_pro
openrouter_openai_chat__deepseek_deepseek_v4_pro
zai_coding_responses__glm_5_3
```

Those are different bindings even when two routes reach the same maker/model. Validation requires the key prefix to match `model.route` and rejects duplicate `(route, upstream_id)` identities. Profile binding accepts only the exact, case-sensitive key. It never accepts a raw upstream ID, a prior key spelling, or a search alias.

Catalogs from any other MMO generation and obsolete provider-qualified keys are invalid. There is no migration reader, lookup fallback, automatic rewrite, or compatibility alias.

OpenRouter itself publishes some exact upstream IDs beginning with `~` for rolling targets. MMO inventories those IDs literally and records their actual maker; it does not make them aliases for another MMO key. Selecting one still requires its exact route-qualified catalog key and accepts the provider-owned rolling semantics.

## Release baseline

The reviewed baseline is current through **2026-08-23** (each inventory retains
its own exact review date):

| Inventory | Records | Notes |
|---|---:|---|
| Installed Codex/ChatGPT catalog | 6 | Built-in models; account visibility remains authoritative |
| OpenCode Go | 29 | Complete public listing joined to reviewed capability evidence |
| OpenCode Zen | 64 | Complete public listing; unsupported Google-native transport stays catalog-only |
| OpenRouter | 422 | Complete reviewed text-input/text-output inventory |
| Z.AI General API | 35 | Includes chat, media, OCR, and hosted-service entries |
| Z.AI Coding Plan | 3 | Current Coding Plan records, including executable GLM-5.3 Responses |
| Local deployment | 1 | Project-capped Qwen3.5-9B llama.cpp route |
| **Total** | **560** | **21 routes, 9 resource groups, 462 agent-compatible models** |

“Supported by an inventory” is not the same as “agent compatible.” Media generators, hosted services, transports the installed driver cannot preserve, and announced-but-unavailable models remain visible without being executable agent bindings.

The canonical source is [config/upstream-inventory.json](../config/upstream-inventory.json). Every external inventory—large or small—uses the same envelope under [config/inventory-snapshots](../config/inventory-snapshots): source captures, fingerprint fields, exact record set, normalized digest, review date, and discovery policy. OpenRouter's reviewed model-to-endpoint selections are part of that declarative discovery policy; the adapter contains no portfolio-specific endpoint map. `scripts/generate_catalog.py` regenerates [config/catalog.toml](../config/catalog.toml) deterministically; release validation fails on drift.

OpenCode Go transport selection uses the explicit provider endpoint table as its authority and Models.dev for capability metadata. The 2026-08-23 sources disagree for four Qwen entries: Models.dev selects its OpenAI-compatible default adapter while the Go table explicitly assigns `/messages` and `@ai-sdk/anthropic`. Each record retains both values, the selected value, authority, and disagreement flag in `protocol_resolution`; that evidence participates in the fingerprint. Live verification reads OpenCode's current documentation branch while the reviewed snapshot retains an immutable commit capture, so later convergence or divergence cannot be hidden by the pinned artifact.

OpenCode currently publishes separate peak and off-peak rates for DeepSeek V4 Flash and Pro. Both rows and labels are retained in snapshot evidence. The scalar catalog cost fields are omitted for those bindings because selecting either time tier as a universal price would be false. The same rule applies to context tiers. A complete dash-valued provider row, such as `ox-alpha-free`, is retained as explicit unpriced evidence rather than converted to zero. Models.dev pricing is not substituted for an absent, dash-valued, or tiered provider billing row. Five live Go IDs (`glm-5`, `kimi-k2.5`, `mimo-v2-omni`, `mimo-v2-pro`, and `qwen3.5-plus`) currently have Models.dev capability evidence but no row in the provider endpoint/pricing table; those gaps remain explicit instead of being filled by inferred provider claims.

## Inventory evidence and uncertainty

The snapshot fingerprint covers IDs, canonical slugs, pricing, limits, modalities, tools, reasoning, structured output, supported parameters, deprecation, and endpoint metadata. Discovery does not infer a capability from a new model name.

Known release-time uncertainties remain explicit:

- the exact authenticated Codex account catalog must be checked on the target installation;
- OpenCode Go's `hy3-preview` live ID has only listing evidence and remains non-agent-compatible;
- five live OpenCode Go IDs lack a current provider endpoint/pricing-table row and therefore use only the transport/capability evidence actually available; `ox-alpha-free` has a complete provider row whose price cells are dashes and is deliberately unpriced;
- four current Go Qwen protocol records have a captured provider-source disagreement, and all Go source/protocol metadata can drift independently of the listing;
- two live OpenCode Zen IDs (`deepseek-v4-flash-free` and `laguna-s-2.1-free`) are present in the live listing and Models.dev but absent from the provider documentation table; they remain executable with `live-undocumented` availability and no inferred scalar price;
- Z.AI Coding Plan availability requires the user’s Coding Plan credential;
- role-specific performance claims require the bundled live evaluations, not catalog metadata.

## Maker, route, gateway, and serving provider

These dimensions answer different questions:

| Dimension | Example | What it tells you |
|---|---|---|
| Maker | `deepseek` | Who created the model family |
| API operator | `opencode` | Who receives the API request |
| Access product | `opencode_go` | Which plan or service is consumed |
| Wire protocol | `openai_chat` | Which request/response contract is used |
| Gateway | Switchyard | Which local adapter translated Codex traffic |
| Serving provider | `parasail/fp8` | Which downstream inference host actually served an aggregator request, when reported |

Model diversity, access diversity, and infrastructure diversity must therefore be evaluated separately. Sending two requests to OpenRouter is not evidence of independent serving infrastructure unless routing policy and response telemetry establish that fact.

## OpenRouter policy and telemetry

An OpenRouter model may declare a typed `route_policy`:

```toml
[models.openrouter_openai_chat__deepseek_deepseek_v4_pro.route_policy]
only = ["parasail/fp8"]
allow_fallbacks = false
require_parameters = true
data_collection = "deny"
zdr = true
quantizations = ["fp8"]
```

The compiler emits the policy mechanically and opts the route into OpenRouter's current `X-OpenRouter-Metadata: enabled` response metadata. Runtime telemetry keeps the requested route/model and policy separate from any selected serving provider or endpoint that survives the full OpenRouter → Switchyard → Codex event path. It ignores unselected provider candidates. OpenRouter's router `attempt` is recorded as a fallback attempt; transport retries are counted only from explicit retry fields. Missing provider, endpoint, retry, or fallback observations remain unknown rather than being inferred from the model maker or retry count. Bundled OpenRouter roles pin an allowlist and disable fallback; route failure is reported instead of silently changing infrastructure.

Switchyard 0.2.0 preserves buffered same-format response extensions but its streaming translation does not expose arbitrary provider metadata as a durable routing-log field. Authenticated end-to-end inference must therefore prove which metadata reaches the installed Codex JSON event stream. Until it does, `route_telemetry.complete = false` and the release evaluation gate fails; a requested endpoint policy is not reported as an observed serving endpoint.

## Z.AI routes

Z.AI General API and Z.AI Coding Plan are distinct access products with distinct credentials:

```text
zai_general_openai_chat  -> ZAI_API_KEY
zai_coding_responses     -> ZAI_CODING_API_KEY
```

The bundled GLM-5.3 roles bind exactly `zai_coding_responses__glm_5_3`. That route uses the Coding Plan's dedicated Codex/Responses base `https://api.z.ai/api/v1`; Switchyard appends `/responses`. The separate OpenAI-compatible Chat route uses `https://api.z.ai/api/coding/paas/v4`. There is no fallback between those paths or to the General API. The General API remains catalogued for operators who independently have that access, including catalog-only media/service endpoints. The Coding Plan Anthropic-compatible route remains catalog-only because the installed Switchyard transport cannot emit its documented Bearer-auth form faithfully.

## Transport conservatism

Model capability and complete-route capability are both required. A model may support images upstream while a particular adapter path is text-only. Such a route advertises only the modalities proven through the complete path. The visual profile uses a ChatGPT-backed Codex route whose image transport is validated end to end; rebinding it to a text-only Go, Zen, OpenRouter, or Z.AI path is rejected.

Reasoning levels are likewise route evidence, not guesses. The internal `none` value means “omit the selector and use upstream defaults”; it does not claim that the model performs no reasoning.

## Layering and local extensions

Catalog resolution order is:

1. bundled `config/catalog.toml`;
2. user fragments in `~/.config/codex-mmo/catalog.d/*.toml`, lexically ordered;
3. an optional profile-local `catalog.toml`;
4. exact `--bind ROLE=MODEL_KEY` overrides.

Later fragments may replace exact keys, after which the complete catalog is revalidated. A fragment may override only a resource, or define full generation-8 route/model records. For example:

```toml
schema_version = 8

[resources.private_pool]
lock_key = "private:engineering"
max_active = 2
description = "Private inference capacity"

[routes.private_openai_chat]
name = "Private OpenAI-compatible service"
api_operator = "example"
access_product = "private_endpoint"
wire_protocol = "openai_chat"
billing_mode = "api"
driver = "switchyard"
base_url = "https://models.example.test/v1"
credential_envs = ["PRIVATE_MODELS_API_KEY"]
resource_group = "private_pool"
transport_modalities = ["text"]
tool_calling = true
supports_custom_tools = true
parallel_tool_calls = false
max_retries = 1

[models.private_openai_chat__example_coder]
display_name = "Example Coder"
description = "Reviewed private coding model"
maker = "example"
route = "private_openai_chat"
upstream_id = "example-coder"
kind = "chat"
availability = "current"
agent_compatible = true
context_window = 131072
modalities = ["text"]
output_modalities = ["text"]
reasoning_levels = ["none"]
default_reasoning = "none"
tool_calling = true
parallel_tool_calls = false
structured_output = false
supports_reasoning_summaries = false
resource_group = "private_pool"
```

Do not set `agent_compatible = true` until tool behavior, output limits, modality preservation, and the exact driver/protocol path have been tested.

`tool_calling` describes ordinary schema-defined function tools. OpenAI Responses
custom tools are a distinct capability: set `supports_custom_tools = false` for
an otherwise tool-capable binding whose endpoint rejects them. With Codex 0.149,
that keeps function-based shell and MCP tools available while omitting the
free-form `apply_patch` tool. The field defaults to `tool_calling`; it cannot be
true when `tool_calling` is false.

## Commands

```bash
codex-mmo catalog summary
codex-mmo catalog routes
codex-mmo catalog models --agent-compatible
codex-mmo catalog models --inventory opencode-go
codex-mmo catalog models --inventory opencode-zen
codex-mmo catalog models --inventory openrouter
codex-mmo catalog model zai_coding_responses__glm_5_3
codex-mmo catalog resources
codex-mmo catalog inventory
codex-mmo catalog verify
```

Compare the release fingerprint with live sources and the authenticated Codex installation:

```bash
codex-mmo catalog verify --remote --codex
```

Refresh observations and optionally install a conservative Codex overlay:

```bash
codex-mmo catalog refresh
codex-mmo catalog refresh --install-codex-overlay
```

Known IDs retain reviewed metadata. Newly observed IDs are reported but not made agent compatible. Stored credentials are sent automatically only to reviewed official origins; custom discovery URLs require explicitly supplied credentials and cross-origin redirects are rejected.

## Cost metadata

Costs are informational and route-specific. When the source supports it, the catalog records base input, cache read, cache creation/write, and output prices independently. Dynamic, tiered, fixed-request, media, or separate reasoning charges are not flattened into a misleading scalar price. Runtime evaluation maintains separate ledgers for subscription usage, local resource usage, and API cost; it does not add unlike units into one “total cost.”
