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:
token_limit_param    = "max-tokens"
input_modalities     = ["text", "image"]
output_modalities    = ["text"]
supported_parameters = []
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

[[rules]]
match = { kind = "any" }
caps.streaming = true
Used for provider-wide defaults (e.g., “every xAI model supports streaming”).
[[rules]]
match = { kind = "exact", model = "deepseek-reasoner" }
caps.supported_parameters = ["include-reasoning"]
Used for per-model overrides that deviate from a sibling family.
[[rules]]
match = { kind = "exact_any", models = ["o1", "o3", "o4"] }
caps.supported_parameters = ["reasoning-effort"]
Used for explicit model enumeration when ids don’t share a prefix.
[[rules]]
match = { kind = "prefix_any", prefixes = ["gpt-4o", "gpt-4.1"] }
caps.input_modalities = ["text", "image"]
caps.json_mode = "schema"
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:
FieldTypeExample
token_limit_paramenummax-tokens · max-completion-tokens · max-output-tokens
input_modalitiesstring[]["text", "image", "audio", "pdf"]
output_modalitiesstring[]["text"] · ["speech"] · ["image-gen"]
tool_callingbooltrue for function/tool use
parallel_tool_callsboolparallel tool invocation
streamingboolServer-Sent Events streaming
json_modeenumunavailable · object · schema
cachingenumnone · prompt-caching · context-caching
supported_parametersstring[]see below

Supported parameter vocabulary (13)

The vocabulary is closed — adding a new parameter is a nika-catalog schema change.
ParameterMeaning
parallel-tool-callsMultiple tools invoked in one step
reasoning-effortOpenAI o-series effort level (low/med/high)
thinking-budgetClaude extended thinking budget tokens
prompt-cachingAnthropic prompt caching (cache_control blocks)
file-searchOpenAI file search tool
web-searchPerplexity / Grok web search tool
streaming-thinkingStream reasoning tokens separately
batch-apiOpenAI / Anthropic batch mode
context-cachingGemini context caching
predicted-outputsOpenAI predicted outputs
computer-useAnthropic computer use tool
citationsPerplexity / Claude citations
include-reasoningDeepSeek include-reasoning flag

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

[defaults]
input_modalities     = ["text", "image"]
output_modalities    = ["text"]
supported_parameters = []

# Rule 1 — all Claude models get prompt caching + 200k context
[[rules]]
scope.providers = ["anthropic", "bedrock"]
match = { kind = "prefix_any", prefixes = ["claude", "anthropic.claude"] }
caps.streaming            = true
caps.tool_calling         = true
caps.parallel_tool_calls  = true
caps.json_mode            = "schema"
caps.caching              = "prompt-caching"
caps.token_limit_param    = "max-tokens"
caps.supported_parameters = ["prompt-caching", "thinking-budget"]

# Rule 2 — claude-opus-4 adds computer-use on top
[[rules]]
scope.providers = ["anthropic"]
match = { kind = "prefix_any", prefixes = ["claude-opus-4"] }
caps.supported_parameters = ["prompt-caching", "thinking-budget", "computer-use", "citations"]

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

nika-cli ships today; the dedicated catalog query surface below is still planned:
$ nika catalog models --provider anthropic
claude-opus-4-5-20260201    streaming · tools · json=schema · cache · thinking · computer-use
claude-sonnet-4-6    streaming · tools · json=schema · cache · thinking
claude-haiku-4-5-20251001   streaming · tools · json=schema · cache

$ nika catalog models --capability computer-use
anthropic/claude-opus-4-5-20260201

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.