AI model selectors with live provider catalogs and validated settings. Install the TypeScript library. Copy the UI into your app.
Try the playground · npm · Runnable starters · Copy a composer · Provider icons · Agent instructions
npm install @axelrock/modelsDiscover a live catalog without an API key:
import { vercelGatewayAdapter } from "@axelrock/models/providers";
const catalog = await vercelGatewayAdapter.discover({
signal: AbortSignal.timeout(10_000),
});
console.log(catalog.models.map(({ id, name }) => ({ id, name })));Models supports OpenRouter, Vercel AI Gateway, OpenAI, Anthropic, and Google AI. Use the core with your own UI, or copy the native web components. An optional AI SDK 7 bridge maps validated selections into call options. Your app owns credentials and sending requests.
The public API is in its initial 0.1 release. Node.js 22 or newer is required.
Model names are the easy part. Reasoning effort, fast or flex service, cache rules, beta contracts, route-specific prices when the source exposes them, and account-specific access are where selectors become unreliable.
Models keeps those facts explicit:
- live discovery for provider model-list endpoints;
supported,unsupported, orunknowninstead of guessed booleans;- dated source evidence on capabilities and decimal-string prices;
- literal TypeScript option values for known models;
- runtime validation for newly discovered models;
- provider-specific mappings without flattening away their meaning;
- native custom elements, with no React dependency;
- deterministic snapshots and a scheduled drift check.
Use @axelrock/models/core for types, validation, policies, and prices, plus
@axelrock/models/providers for provider discovery and request mappings.
@axelrock/models/ai-sdk is an optional bridge for AI SDK 7 applications.
These are subpath imports from one package, @axelrock/models, with one version.
The root import also exports core. Provider code is loaded only through /providers.
AI SDK is an optional peer dependency, required only when using /ai-sdk.
The package contains no UI or bundled brand icons.
Install the package:
npm install @axelrock/modelsThe gallery's distribution/ui-source.json provides the UI source files with
imports from @axelrock/models/core. Copy all files, preserve their paths, and use a
TypeScript-capable bundler. Import defineModelsElements from your local
ui/index.ts. Import provider adapters from @axelrock/models/providers.
Keep the license notices. The internal @models/elements workspace package is
private and is not part of the release set.
For a no-install experiment, the gallery still offers models.js and a complete
HTML example. That standalone file includes logic, UI, icons, and dependencies;
replace it as a whole to update it. The package path lets you update logic
without overwriting customized UI source. Review package changes and test your
integration when updating.
pnpm check packs and installs the release package in a temporary consumer,
checks exports and real discovery parsing, and verifies the site's source and
icon files. pnpm prepare:packages prepares archives only; it never publishes.
| Import | Purpose |
|---|---|
@axelrock/models/core |
Catalog, evidence, option, validation, price, and drift primitives |
@axelrock/models/providers |
Five first-party discovery adapters and generated gateway model IDs |
@axelrock/models/ai-sdk |
Optional bridge for AI SDK model factories and provider options |
Gateway discovery needs no key:
import { openRouterAdapter, vercelGatewayAdapter } from "@axelrock/models/providers";
const [openRouter, gateway] = await Promise.all([
openRouterAdapter.discover(),
vercelGatewayAdapter.discover(),
]);Direct providers need server-side credentials because their list endpoints are authenticated:
import { anthropicAdapter } from "@axelrock/models/providers";
const catalog = await anthropicAdapter.discover({
apiKey: process.env.ANTHROPIC_API_KEY,
});Never pass provider keys to browser components. Fetch a catalog on your server, then send only the catalog fields your browser needs.
import { selectModel } from "@axelrock/models/core";
import { openAiAdapter } from "@axelrock/models/providers";
const catalog = await openAiAdapter.discover({ apiKey: process.env.OPENAI_API_KEY });
const model = catalog.models.find((item) => item.id === "gpt-5.6-luna");
if (model) {
const selection = selectModel(model, {
"reasoning.effort": "high",
"service.tier": "flex",
});
const request = openAiAdapter.mapOptions(selection.model, selection.options);
}Generated KnownVercelModelId and KnownOpenRouterModelId unions describe the
reviewed snapshots. selectKnownGatewayModel(catalogs, key, options) connects a
reviewed provider-qualified key to its exact generated option type. Live IDs
can be newer, so discovered records still use runtime validation.
A catalog entry whose provider ID includes fast is a distinct model or route.
A speed.mode or service.tier control is a runtime option on one model. The
library preserves that difference and never invents one from the other.
The optional AI SDK bridge returns callOptions that can be spread directly:
const prepared = prepareAiSdkCall(adapter, selection, provider);
const result = await generateText({
model: prepared.model,
...prepared.callOptions,
prompt: "Hello",
});import { defineModelsElements } from "@models/elements";
defineModelsElements();
const picker = document.querySelector("models-picker");
picker.catalogs = [catalog];
picker.groups = ["reasoning", "speed", "caching"];
picker.groupBy = "author"; // Optional for multi-provider gateways.
picker.addEventListener("models-selection-change", (event) => {
console.log(event.detail);
});models-options-change emits every option draft so a host can save work in
progress. models-selection-change emits only after all option and cross-field
rules pass.
Use <models-select> for a compact branded combobox, <models-options> for a
detail panel, <models-composer> for progressive disclosure, <models-price>
for cost context, or <models-picker> for the complete inspector. CSS custom
properties and ::part() hooks provide styling without making the package
framework-specific.
Use exact provider model IDs or provider-qualified keys to keep the available set explicit and easy to review in code.
import {
defineModelPolicy,
defineModelPolicyFor,
findLowestPricedModel,
resolveModelPolicy,
resolvePolicyDefaults,
} from "@axelrock/models/core";
const policy = defineModelPolicy({
models: {
include: ["anthropic/claude-opus-5", "openai/gpt-5.6-sol", "moonshotai/kimi-k3"],
},
options: {
groups: ["reasoning", "speed", "routing"],
values: {
"reasoning.effort": ["low", "medium", "high"],
"service.tier": ["default", "flex", "fast"],
"speed.mode": ["standard", "fast"],
},
defaults: {
"reasoning.effort": "medium",
"service.tier": "default",
"speed.mode": "standard",
},
},
});
const curated = resolveModelPolicy(catalogs, policy);
const lowestInput = findLowestPricedModel(curated.catalogs, "input-token");
selector.catalogs = curated.catalogs;
composer.catalogs = curated.catalogs;
composer.groups = curated.groups;
const defaults = selectedModel && resolvePolicyDefaults(selectedModel, curated);
selector.recommendations = [
{
model: "anthropic/claude-opus-5",
label: "Recommended for this app",
},
...(lowestInput === undefined
? []
: [{ model: lowestInput.key, label: "Lowest listed input price" }]),
];
if (curated.diagnostics.length > 0) {
console.warn(curated.diagnostics);
}Use defineModelPolicyFor(generatedCatalogs, policy) when catalog model IDs and
options are known at build time. It rejects misspelled IDs, option keys, values,
and defaults in TypeScript. Live runtime catalogs use defineModelPolicy plus
the resolver diagnostics shown above.
The input-price helper compares only unconditional model-level rates for the
requested unit. It does not guess route prices, conditional tiers, or a blended
workload, so the UI never presents a broad Cheapest claim without evidence.
Model curation and option visibility are separate axes. Resolve one policy, then pass the same result to each presentation:
minimal.catalogs = curated.catalogs;
inline.catalogs = curated.catalogs;
inlineOptions.groups = curated.groups;
composer.catalogs = curated.catalogs;
composer.groups = curated.groups;Curated enum values are intersected with each model's evidence-backed values. Curated defaults are app-owned initial values, not changes to provider facts. An empty value intersection removes that option, and runtime validation rejects a value excluded by the application policy. Diagnostics report model IDs, option keys, values, defaults, and recommendations that do not match the supplied catalogs, so dynamic policies do not fail silently.
pnpm install
pnpm devThe gallery presents Minimal, Standalone, Chat, and Inspector shapes in one tabbed surface. A shared policy controls approved models and optional detail groups across every shape. Catalog-source controls and evidence stay in a separate developer disclosure. The page loads public gateway catalogs live and uses documented demo records for direct providers, so no credential can enter the browser.
pnpm catalog:check
pnpm catalog:refresh
pnpm catalog:auditcatalog:check compares current public gateway responses with reviewed
snapshots and fingerprints the official documents behind option overlays.
catalog:refresh updates both records and the generated model and option types.
It does not publish or merge anything. Direct-provider option overlays are
reviewed from official documentation because their availability can depend on
the account, endpoint, model, and region.
catalog:audit also compares raw public response shapes and live enums. It
catches new provider fields that the normalized adapter does not understand
yet. Use the repository update-model-catalog skill to review a reported
change; scripts detect facts, while the skill guides the human-reviewed update.
See architecture, provider coverage, and maintenance for the boundaries behind these choices.
Node 22 or newer and pnpm 10 are required. Run pnpm check before opening a
pull request. See CONTRIBUTING.md and
SECURITY.md.
MIT
