#12557·goose

Resolve model capabilities for custom providers via catalog_provider_id

Author: Innsaute-glitchCreated Sep 28, 2026Updated Sep 28, 2026

Problem

For a custom/declarative OpenAI-engine provider, ModelConfig.supports_vision is None and only resolves from the canonical catalog. #12522 wired with_canonical_vision_support(provider_name) into apply_canonical_limits, which helps providers whose name matches a canonical id — but, as noted in #12520, "an arbitrarily-named custom provider never will". So a user with, say, gpt-5.6-luna served through a personal gateway still gets images stripped.

The reverse case is worse: a provider whose name does resolve (openai, xai, …) works, so behaviour depends on whether the user happened to name their provider after a catalogued host.

Proposal

Use the already-present catalog_provider_id field on custom provider configs as a fallback identity for canonical capability resolution. When the configured provider_name isn't in the catalog, resolve using catalog_provider_id instead.

catalog_provider_id already exists (DeclarativeProviderConfig.catalog_provider_id: Option<String>) and is consumed today for model-list sourcing and provider matching — but not in the capability/vision path.

Sketch

Thread the id into resolution, e.g.:

rust
// model_config.rs
fn apply_canonical_limits(provider_name: &str, model: ModelConfig) -> ModelConfig {
    if provider_name == goose_providers::azure_foundry::AZURE_FOUNDRY_PROVIDER_NAME {
        return model;
    }
    let canonical_provider = catalog_provider_id_for(provider_name).unwrap_or(provider_name);
    model
        .with_canonical_limits(canonical_provider)
        .with_canonical_vision_support(canonical_provider)
}

where catalog_provider_id_for looks up the loaded declarative provider config (already available) and returns its catalog_provider_id when set. Existing behaviour is preserved when it's unset.

This composes with #12183 (per-model modalities): explicit config should still win, with catalog_provider_id as the next fallback, then ["text"].

Evidence (measured on v1.52.0 / main @ 98c626d)

  • Provider name not in the catalog → supports_vision None → tool-result images replaced by the omission text; the outgoing request has no image part.
  • Adding exact <provider>/<model> catalog entries flips it to supports_vision == Some(true) and the request carries a real image_url data part. Confirms the resolution path works and the only gap is how a custom provider tells goose which catalog identity to use.

Alternatives / complements

  • #12183 (per-model modalities) — the most direct fix for arbitrary providers; this issue is complementary for the many custom providers that are simple gateways in front of a catalogued host.
  • Documenting "name your custom provider after the upstream host" — reject as a footgun.

Caveats

  • If catalog_provider_id maps to a host whose catalog entries are stale, capabilities could be wrong; explicit config (#12183) should take precedence.
  • Resolution must remain env-overridable (GOOSE_*) and must not clamp limits/pricing unintentionally (same caution as the supports_vision path).