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
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.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.
The 4 match kinds
`any` — matches every model
`any` — matches every model
`exact` — matches one model id
`exact` — matches one model id
`exact_any` — matches a set of model ids
`exact_any` — matches a set of model ids
`prefix_any` — matches models whose id starts with any prefix
`prefix_any` — matches models whose id starts with any prefix
Capability fields
What a rule can set:| Field | Type | Example |
|---|---|---|
token_limit_param | enum | max-tokens · max-completion-tokens · max-output-tokens |
input_modalities | string[] | ["text", "image", "audio", "pdf"] |
output_modalities | string[] | ["text"] · ["speech"] · ["image-gen"] |
tool_calling | bool | true for function/tool use |
parallel_tool_calls | bool | parallel tool invocation |
streaming | bool | Server-Sent Events streaming |
json_mode | enum | unavailable · object · schema |
caching | enum | none · prompt-caching · context-caching |
supported_parameters | string[] | see below |
Supported parameter vocabulary (13)
The vocabulary is closed — adding a new parameter is a
nika-catalog schema change.| Parameter | Meaning |
|---|---|
parallel-tool-calls | Multiple tools invoked in one step |
reasoning-effort | OpenAI o-series effort level (low/med/high) |
thinking-budget | Claude extended thinking budget tokens |
prompt-caching | Anthropic prompt caching (cache_control blocks) |
file-search | OpenAI file search tool |
web-search | Perplexity / Grok web search tool |
streaming-thinking | Stream reasoning tokens separately |
batch-api | OpenAI / Anthropic batch mode |
context-caching | Gemini context caching |
predicted-outputs | OpenAI predicted outputs |
computer-use | Anthropic computer use tool |
citations | Perplexity / Claude citations |
include-reasoning | DeepSeek include-reasoning flag |
Modality vocabulary (8)
text · image · audio · video · pdf · embedding · speech · image-gen
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.How to query capabilities
nika-cli ships today; the dedicated catalog query surface below is still planned:
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.