# Phase 07 — Model catalog

**Estimated time:** ~15 minutes
**Touches:** `lib/dispatch/adapter/minimax/model_catalog.rb`,
`lib/dispatch/adapter/minimax.rb` (only if `list_models` references the
catalog).

## Goal

Update `ModelCatalog.build` and `ModelCatalog.build_from_api` so they
reflect MiniMax's seven supported models and capabilities (per their
docs). The class shape stays the same so callers / specs keep working.

MiniMax compatibility doc capability flags:

- All 7 models support tool calls.
- All 7 models support streaming.
- Vision is NOT supported in any model (image / document content blocks
  are explicitly listed as unsupported).
- `premium_request_multiplier` is `nil` (Token Plan is request-quota,
  not multiplier-weighted).

## Steps

### 1. Update `lib/dispatch/adapter/minimax/model_catalog.rb`

Two changes:

- Set `supports_vision: false` (MiniMax does not accept image blocks).
- Drop the `"(unrated) #{display_name}"` formatting in
  `build_from_api`. With every model in the bundled pricing table, the
  `nil` branch is unreachable in practice, but keep the guard:
  `display_name` should be returned verbatim. Unknown runtime models
  should still produce a usable `ModelInfo`.

Final shape:

```ruby
# frozen_string_literal: true

module Dispatch
  module Adapter
    class MiniMax < Base
      module ModelCatalog
        DEFAULT_CONTEXT_WINDOW = 204_800

        module_function

        # Build a ModelInfo for a known MiniMax model id.
        #
        # @param id [String]
        # @return [Dispatch::Adapter::ModelInfo]
        def build(id)
          ModelInfo.new(
            id: id,
            name: id,
            max_context_tokens: PricingTable.context_window(id) || DEFAULT_CONTEXT_WINDOW,
            supports_vision: false,
            supports_tool_use: true,
            supports_streaming: true,
            premium_request_multiplier: nil,
            pricing: PricingTable.lookup(id)
          )
        end

        # Build a ModelInfo from a runtime API entry (from GET /v1/models).
        #
        # If the model id is in the bundled pricing table, the bundled
        # context window is used. Display name from the API entry takes
        # precedence over the id.
        #
        # @param api_entry [Hash]
        # @return [Dispatch::Adapter::ModelInfo]
        def build_from_api(api_entry)
          id           = api_entry["id"].to_s
          display_name = api_entry["display_name"].to_s
          display_name = id if display_name.empty?

          pricing     = PricingTable.lookup(id)
          context_win = PricingTable.context_window(id) || DEFAULT_CONTEXT_WINDOW

          ModelInfo.new(
            id: id,
            name: display_name,
            max_context_tokens: context_win,
            supports_vision: false,
            supports_tool_use: true,
            supports_streaming: true,
            premium_request_multiplier: nil,
            pricing: pricing
          )
        end
      end
    end
  end
end
```

### 2. Sanity-check `list_models`

Find `def list_models` in `lib/dispatch/adapter/minimax.rb`. It probably
calls `ModelCatalog.build_from_api(entry)` for each runtime entry, then
merges with the hardcoded catalog. Confirm:

- The runtime fetch hits `MODELS_PATH` (`/v1/models`).
- On any error (404, parse failure, network), it falls back to building
  `ModelInfo` for each id from `PricingTable.known_ids` via
  `ModelCatalog.build`.
- The result is a deduplicated array (by `id`).
- Caching (`@models_cache`, `@models_cache_at`) is intact.

If MiniMax doesn't expose `/v1/models` (we don't know yet), the runtime
branch will return `nil` / `[]` and the hardcoded catalog will be used.
Both outcomes are acceptable.

DO NOT add live network smoke testing here; live testing is post-plan.

## Acceptance criteria

- `ModelCatalog.build("MiniMax-M2.7")` returns a `ModelInfo` with:
  - `id: "MiniMax-M2.7"`
  - `max_context_tokens: 204_800`
  - `supports_vision: false`
  - `supports_tool_use: true`
  - `supports_streaming: true`
  - `pricing` with all four `*_per_mtok` fields zero.
- `ModelCatalog.build("not-a-real-model")` returns a `ModelInfo` with
  `pricing: nil` and `max_context_tokens: 204_800` (the default).
- `bundle exec rubocop --autocorrect-all` exits 0.

## Verification

Run `run_tests` with `project_path=reference/dispatch-adapter-minimax`.
Rubocop must be clean. `model_catalog_spec.rb` failures are acceptable
(retargeted in phase 16). Any failure outside that file must be fixed
before calling `ask_for_next_plan`.
