Skip to main content
Nika’s capability system answers one question: given a provider + model, what features does it support? Tool calling? Vision input? Audio output? JSON schema mode? Thinking budgets? Prompt caching? The answer is a ModelCapabilities struct, computed at runtime by applying rules (in file order) on top of a defaults block. Zero regex, zero allocation, zero runtime deps.
Canonical source: crates/nika-catalog/data/model-capabilities.toml. Schema: nika/model-capabilities@1.0. Build-time FK checks against llm-providers.toml (unknown provider = build failure, catches typos).

Resolution algorithm

1

Start from `[defaults]`

Mirrors ModelCapabilities::default(). Current defaults:
2

Scan `[[rules]]` in file order

Every rule has a match clause and a caps clause. If the rule’s scope.providers list is non-empty and the canonical provider id isn’t in it → skip. Same for scope.api_dialect.
3

Apply the rule's `caps` fields

Fields set by a rule overwrite earlier values. Fields absent from the rule preserve the accumulated state. No merging of lists: assignment is wholesale.
4

Return the accumulated `ModelCapabilities`

Zero allocation (every string is &'static str or Cow<'static, str>). Resolved at runtime in O(N) rules for a single lookup.

The 4 match kinds

Used for provider-wide defaults (e.g., “every xAI model supports streaming”).
Used for per-model overrides that deviate from a sibling family.
Used for explicit model enumeration when ids don’t share a prefix.
Most common. Covers entire model families in one rule. No regex: prefix match only (keeps zero runtime deps in L0).

Capability fields

What a rule can set:

Supported parameter vocabulary (13)

The vocabulary is closed: adding a new parameter is a nika-catalog schema change.

Modality vocabulary (8)

text · image · audio · video · pdf · embedding · speech · image-gen
input_modalities must contain text (build invariant). A pure-image input model can’t be wired as an infer: target without text support.

Example resolution trace

Why not regex

L0 crates ship zero runtime dependencies beyond std + alloc. Regex would pull in regex-automata (~50k LOC) and force a runtime initialization cost. prefix_any covers 95% of actual model-family matching needs; the remaining 5% is handled by exact_any.
This is Q1 (no proc macros) applied at the data-layer. See L0 foundation decisions.

How to query capabilities

Two shipped subcommands project the embedded catalogs — nika catalog (providers · models · capabilities · env vars) and nika catalog --tools (the nika:* builtin schemas), each with a versioned --json machine form:

Drift protection

Build-time FK check: every scope.providers[i] must be a canonical provider id in llm-providers.toml. Typos fail the build.Build-time enum check: every supported_parameters entry must be in the 13-value vocabulary. New additions require a schema bump.Build-time ordering check: tags arrays in llm-providers.toml must be sorted + deduplicated. Same applies to caps.* arrays by convention.

See also

Providers catalog

The canonical providers these rules target.

Concepts · Providers

Runtime routing: how infer verb picks a provider.

Schema

.nika.yaml envelope + workflow schema reference.

L0 decisions

Q1-Q13: why no regex, why manual impl, why prefix-only matching.