zeroclaw-labs/zeroclaw · error · anyhow::Error

OpenRouter catalog has no entries under vendor prefix {vendo

Error message

OpenRouter catalog has no entries under vendor prefix {vendor_prefix:?}

What it means

list_models_for_vendor fetched OpenRouter's public /api/v1/models catalog successfully, then filter_by_vendor found zero model ids starting with "{vendor_prefix}/". The vendor prefix is the segment before the first slash in an OpenRouter id (e.g. anthropic, openai, x-ai); the call errors rather than returning an empty list so callers never mistake 'no such vendor' for 'vendor has no models'.

Source

Thrown at crates/zeroclaw-providers/src/openrouter_catalog.rs:94

        .into_iter()
        .map(|m| ModelEntryWithPricing {
            id: m.id,
            pricing: m.pricing,
        })
        .collect())
}

/// Filter a parsed catalog by vendor prefix, returning the slug portion of
/// each match. Sorted and deduped. Errors if nothing matches. Pure —
/// separated from the live fetch so it can be unit-tested.
pub(crate) fn filter_by_vendor(catalog: &[String], vendor_prefix: &str) -> Result<Vec<String>> {
    let needle = format!("{vendor_prefix}/");
    let mut slugs: Vec<String> = catalog
        .iter()
        .filter_map(|id| id.strip_prefix(&needle).map(ToString::to_string))
        .collect();
    if slugs.is_empty() {
        anyhow::bail!("OpenRouter catalog has no entries under vendor prefix {vendor_prefix:?}");
    }
    slugs.sort();
    slugs.dedup();
    Ok(slugs)
}

/// Filter an enriched catalog by vendor prefix, returning model entries with
/// pricing. Sorted and deduped by id.
fn filter_by_vendor_with_pricing(
    catalog: &[ModelEntryWithPricing],
    vendor_prefix: &str,
) -> Result<Vec<zeroclaw_api::model_provider::ModelInfo>> {
    use zeroclaw_api::model_provider::ModelInfo;
    let needle = format!("{vendor_prefix}/");
    let mut models: Vec<ModelInfo> = catalog
        .iter()
        .filter_map(|e| {
            e.id.strip_prefix(&needle).map(|slug| ModelInfo {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Pass only the segment before the slash: "anthropic", not "anthropic/" and not the full model id.
  2. List the full catalog (list_models) and read the real prefixes before filtering.
  3. If the prefix still fails, confirm the vendor exists at openrouter.ai/models — it may have been renamed or delisted.

Example fix

// before — trailing slash and full-id forms match nothing
list_models_for_vendor("anthropic/").await?;

// after — bare vendor segment
list_models_for_vendor("anthropic").await?;
Defensive patterns

Strategy: validation

Validate before calling

// Verify the prefix exists before filtering
let catalog = list_models().await?;
let needle = format!("{vendor}/");
anyhow::ensure!(
    catalog.iter().any(|id| id.starts_with(&needle)),
    "unknown vendor {vendor}; call list_models() to see real prefixes"
);

Type guard

fn vendor_exists(catalog: &[String], vendor: &str) -> bool {
    catalog.iter().any(|id| id.starts_with(&format!("{vendor}/")))
}

Try / catch

Catch the empty-filter error and fall back to listing the full catalog so the user can pick a real vendor prefix; do not retry — the fetched catalog is cached (OnceCell) and will not change within the process.

Prevention

When it happens

Trigger: Calling list_models_for_vendor with a trailing slash ("anthropic/" makes the needle "anthropic//" and matches nothing); a misspelled vendor slug ("google-deepmind" vs "google"); passing a full model id ("anthropic/claude-3.5-sonnet") as the vendor argument.

Common situations: Hardcoding vendor names guessed from marketing names instead of reading actual catalog ids; OpenRouter renaming or delisting a vendor; code written against an old catalog snapshot.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/edf409657c3ed027. Report an issue: GitHub.