Skip to content

[Feature Request] Auto-discover models from openai-compatible custom providers #2983

Description

@jamesETsmith

Description

Auto-discover models from custom openai-compat providers via /v1/models

Summary

Allow custom providers (let's call them openai-compat) in crush.json to omit the models array and have Crush populate it automatically by querying the provider's standard GET /v1/models endpoint at load time.

Motivation

Today, every custom provider must enumerate its models by hand:

{
  "providers": {
    "gateway": {
      "type": "openai-compat",
      "base_url": "https://example.com/v1",
      "api_key": "$GATEWAY_API_KEY",
      "models": [
        { "id": "model-a", "context_window": 200000, ... },
        { "id": "model-b", "context_window": 128000, ... },
        // …dozens more entries that change every few weeks
      ]
    }
  }
}

If the array is empty, internal/config/load.go:374-379 silently deletes the provider:

if len(providerConfig.Models) == 0 {
    slog.Warn("Skipping custom provider because the provider has no models", "provider", id)
    c.Providers.Del(id)
    continue
}

Catwalk solves this for built-in providers, but custom / corporate / proxy gateways (LiteLLM, vLLM, llama.cpp, LM Studio, OpenRouter mirrors, internal company gateways, etc.) are on their own. Users end up writing wrapper scripts that hit /v1/models and rewrite crush.json — exactly the kind of glue Crush should subsume.

Most OpenAI-compatible servers already implement GET /v1/models per the OpenAI spec, returning at minimum a list of {id, object, created, owned_by} entries. That is enough to seed a provider's model list without any per-model hand configuration.

Proposed behavior

  1. Trigger: When a custom provider has "models": [] (or the field is omitted) and type is openai-compat / openai / anthropic, Crush calls GET {base_url}/models using the configured api_key and extra_headers during config load.
  2. Mapping: Each returned id becomes a model entry. Unknown fields (context_window, default_max_tokens, cost_per_1m_*, can_reason, supports_attachments) get sensible defaults — same defaults Crush already uses when those keys are omitted in the config today.
  3. Overrides: A new optional model_defaults object on the provider lets users tune the per-family fallbacks without listing every model. A new optional model_overrides map keyed by model id lets users override individual models without re-enumerating the rest.
  4. Opt-out: A provider-level "discover_models": false (default true when models is empty) preserves the current behavior of failing closed — useful if a gateway's /v1/models is slow or unreliable and the user prefers to enumerate models by hand.
  5. No on-disk cache. /v1/models is effectively free on every provider we've checked (one GET per process launch, sub-second). Skipping the cache avoids stale-data bugs and TTL knobs; users who need offline startup can enumerate models explicitly, just like today.

Example config

{
  "providers": {
    "gateway": {
      "type": "openai-compat",
      "base_url": "https://example.com/v1",
      "api_key": "$GATEWAY_API_KEY",
      "extra_headers": { "X-Custom-Header": "$GATEWAY_API_KEY" },
      "model_defaults": { "context_window": 128000, "default_max_tokens": 4096 },
      "model_overrides": {
        "model-a": { "context_window": 200000, "supports_attachments": true },
        "model-b": { "context_window": 400000, "can_reason": true }
      }
    }
  }
}

No models array, no manual sync — Crush asks the gateway what it offers.

Why not "just maintain a script"

That is the current workaround, and it's what I'm doing now. It works, but:

  • Every Crush user behind a corporate OpenAI-compatible gateway re-implements the same script.
  • The script has to know Crush's config schema and risks corrupting crush.json on partial writes.
  • It can't react when the gateway adds a model mid-session.
  • It defeats the "drop in a base_url, go" UX Crush already offers for built-in providers.

Related issues

This issue generalizes #2740 to any OpenAI-compatible custom provider.

Acceptance criteria

  • Custom provider with empty models and reachable /v1/models loads successfully with the discovered list.
  • Discovered models honor model_defaults and per-id model_overrides.
  • Unreachable / non-200 /v1/models emits the existing "no models configured" warning and skips the provider as today (fail-closed, no silent partial state).
  • discover_models: false preserves current behavior exactly.

Willing to contribute

Happy to take a stab at a PR if maintainers agree on the shape above.

Metadata

Metadata

Assignees

No one assigned
    No fields configured for feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions