@cogitator-ai/models
v18.6.0
Published
Dynamic model registry with pricing for Cogitator
Maintainers
Readme
@cogitator-ai/models
Dynamic model registry with pricing information for Cogitator. Fetches up-to-date model data from LiteLLM and OpenRouter's public model list, and works offline from built-in models and bundled copies of both.
Installation
pnpm add @cogitator-ai/models@cogitator-ai/core uses this registry to price runs (result.usage.cost), under the prices of your own in llm.pricing, and to pick models for cost-aware routing. See Model Registry on the website.
Features
- Dynamic Data - Fetches latest model info from LiteLLM, and
openrouter/*prices from OpenRouter's own model list - Pricing Information - Input, output and prompt-cache costs per million tokens,
calculateCost()prices token usage,ModelPricerputs prices of your own over the registry - Capability Tracking - Vision, tools, streaming, JSON mode support
- Multi-Provider - OpenAI, Anthropic, Google, Ollama, Azure, AWS, and more
- Caching - Memory or file-based cache with configurable TTL
- Offline Catalogue - Built-in models and bundled LiteLLM and OpenRouter snapshots when external data is unavailable
- Filtering - Query models by provider, capabilities, price
Quick Start
import { initializeModels, getModel, getPrice, listModels } from '@cogitator-ai/models';
await initializeModels();
const model = getModel('gpt-6.1-sol');
console.log(model?.contextWindow);
console.log(model?.capabilities?.supportsVision);
const price = getPrice('claude-sonnet-5-5');
console.log(`Input: $${price?.input}/M tokens`);
console.log(`Output: $${price?.output}/M tokens`);
const toolModels = listModels({
supportsTools: true,
provider: 'openai',
});Lookups are case-insensitive and resolve aliases. Calling them before initializeModels() works too: the registry then uses the offline catalogue, with the same lookup rules, and initializeModels() still loads the current one whenever it runs.
Provider-qualified ids
The registry keeps one entry per provider, so a model sold by several providers keeps each provider's price. id is the model's name at its provider and catalogId the key its catalogue lists it under (openrouter/<id> for a model of OpenRouter's list):
await initializeModels();
getModel('openrouter/deepseek/deepseek-v4-pro');
// { id: 'deepseek/deepseek-v4-pro', provider: 'openrouter',
// catalogId: 'openrouter/deepseek/deepseek-v4-pro',
// pricing: { input: 0.888966, output: 1.777932, inputCached: 0.0740805 }, ... }
getModel('deepseek/deepseek-v4-pro'); // DeepSeek's own price, { input: 1.32, output: 3.96, ... }
getModel('openai/gpt-6.1-sol'); // Cogitator model strings work
getModel('azure_ai/deepseek-v4-pro'); // and so do LiteLLM keys, at that provider's price- A provider-qualified id finds that provider's entry.
openrouter/*models are priced from OpenRouter's public model list (https://openrouter.ai/api/v1/models, no key needed), as OpenRouter bills them, not at the vendor's direct price. OpenRouter's vendor namesz-aiandx-aiare read as the providerszaiandxai. - When that provider does not list the model, the prefix is dropped and the rest is looked up (
openai/deepseek/deepseek-v4-prois priced asdeepseek/deepseek-v4-pro). - A bare name picks one listing by fixed rules, never by catalogue order: active before deprecated, the vendor's own listing (a built-in model or an unprefixed LiteLLM key) before resellers, then the providers in
BUILTIN_PROVIDERSorder, then the shorter id, then alphabetically.
Price runs with the full model string the agent runs on (calculateCost('openrouter/deepseek/deepseek-v4-pro', usage)).
Model Registry
The ModelRegistry class manages model data with caching and auto-refresh.
Initialization
import { ModelRegistry } from '@cogitator-ai/models';
const registry = new ModelRegistry({
cache: {
ttl: 24 * 60 * 60 * 1000,
storage: 'file',
filePath: './cache/models.json',
},
autoRefresh: true,
refreshInterval: 24 * 60 * 60 * 1000,
fallbackToBuiltin: true,
});
await registry.initialize();Configuration Options
interface RegistryOptions {
cache?: CacheOptions;
autoRefresh?: boolean;
refreshInterval?: number;
fallbackToBuiltin?: boolean;
}
interface CacheOptions {
ttl: number;
storage: 'memory' | 'file';
filePath?: string;
}| Option | Default | Description |
| ------------------- | -------------------------------- | ------------------------------------ |
| cache.ttl | 24 hours | Cache time-to-live in milliseconds |
| cache.storage | 'memory' | Storage backend |
| cache.filePath | ~/.cogitator/models-cache.json | File path for file-based cache |
| autoRefresh | false | Enable automatic background refresh |
| refreshInterval | 24 hours | Refresh interval in milliseconds |
| fallbackToBuiltin | true | Use the offline catalogue on failure |
initialize() uses a fresh cache when there is one (and refreshes in the background), otherwise fetches LiteLLM data and OpenRouter's model list, fetched models are merged with the built-in ones, OpenRouter's prices over LiteLLM's for openrouter/*. When the LiteLLM fetch fails it falls back to a stale cache, then to the offline catalogue (or throws with fallbackToBuiltin: false). When only OpenRouter cannot be fetched, the bundled copy of its list takes its place, over LiteLLM as offline, with a warning, and the catalogue stays fresh in the cache for 15 minutes instead of the whole ttl, so the next load asks OpenRouter again.
Offline Catalogue
The offline catalogue is the built-in models plus every priced model of the copy of the LiteLLM catalogue bundled with the package (LITELLM_SNAPSHOT, with LiteLLM's MIT notice) and of the copy of OpenRouter's model list (OPENROUTER_SNAPSHOT, the priced models that answer in text), the built-in models priced and sized from those copies. A model the live catalogue lists without a price keeps its context window and capabilities but carries priceUnknown: true, and getPrice, getPricing and calculateCost answer null for it instead of a price of zero. It answers lookups before initializeModels() and when both the fetch and the cache fail, so DeepSeek, Mistral, Bedrock, OpenRouter and the rest are priced without the network.
import { getPricing, LITELLM_SNAPSHOT, OPENROUTER_SNAPSHOT } from '@cogitator-ai/models';
getPricing('deepseek/deepseek-chat'); // from the bundled copy, before initializeModels()
LITELLM_SNAPSHOT.fetchedAt; // the day the copy was made, 'YYYY-MM-DD'
LITELLM_SNAPSHOT.source; // the LiteLLM catalogue URL at the commit the copy was made from
OPENROUTER_SNAPSHOT.fetchedAt; // the day the OpenRouter copy was madepnpm --filter @cogitator-ai/models snapshot refreshes both copies in the repository, keeping the models that answer text and the fields the registry reads.
Registry Methods
await registry.initialize();
const model = registry.getModel('gpt-6.1-sol');
const price = registry.getPrice('claude-sonnet-5-5'); // { input, output } per million tokens
const pricing = registry.getPricing('claude-sonnet-5-5'); // full ModelPricing incl. cache prices
const models = registry.listModels({
provider: 'anthropic',
supportsVision: true,
});
const providers = registry.listProviders();
const provider = registry.getProvider('openai');
console.log(registry.getModelCount());
console.log(registry.isInitialized());
await registry.refresh();
registry.shutdown();Global Functions
For convenience, the package provides global functions that use a default registry (file cache at ~/.cogitator/models-cache.json, 24 h TTL, offline fallback):
import {
initializeModels,
getModel,
getPrice,
getPricing,
calculateCost,
isDecisionModel,
listModels,
getModelRegistry,
shutdownModels,
} from '@cogitator-ai/models';
await initializeModels();
const model = getModel('gpt-6.1-sol');
const price = getPrice('gpt-6.1-sol');
const pricing = getPricing('gpt-6.1-sol');
const allModels = listModels();
// USD for a call; cached and cache-write tokens are parts of inputTokens
const cost = calculateCost('claude-sonnet-5-5', {
inputTokens: 12_000,
outputTokens: 800,
cachedInputTokens: 10_000,
cacheWriteTokens: 0,
cacheWrite1hTokens: 0, // part of cacheWriteTokens written with the 1-hour TTL
}); // null when the model's price is unknown
isDecisionModel('openrouter/typesafe/jev-1.13'); // true: it answers through cog.decide(), not chat
const registry = getModelRegistry();
const count = registry.getModelCount();
shutdownModels();Prices of Your Own
ModelPricer prices model calls from prices of your own first and the registry second, the way @cogitator-ai/core does with llm.pricing. A key matches the model string or its tail after any /, the longest match first, case-insensitively: openai/gpt-4o prices that model string and the ones that end in it, such as openrouter/openai/gpt-4o, gpt-4o the model at every provider that serves it under that name.
import { ModelPricer, costOf, getPricing, type PricingOverrides } from '@cogitator-ai/models';
const overrides: PricingOverrides = { 'my-finetune': { input: 0.3, output: 1.2 } };
const pricer = new ModelPricer(overrides);
pricer.pricing('openai/my-finetune'); // { input: 0.3, output: 1.2 }
pricer.cost('openai/my-finetune', { inputTokens: 10_000, outputTokens: 500 }); // 0.0036, null when unknown
pricer.hasOverride('claude-sonnet-5-5'); // false: priced from the registry
costOf(getPricing('claude-sonnet-5-5')!, { inputTokens: 1000, outputTokens: 100 }); // USD at a given priceModel Information
ModelInfo Type
interface ModelInfo {
id: string; // the model's name at its provider
provider: string;
catalogId?: string; // the LiteLLM key, e.g. 'openrouter/deepseek/deepseek-v4-pro'
displayName: string;
pricing: ModelPricing;
contextWindow: number;
maxOutputTokens?: number;
capabilities?: ModelCapabilities;
deprecated?: boolean;
aliases?: string[];
kind?: 'chat' | 'decision'; // decision models answer typed questions through cog.decide()
}
interface ModelPricing {
input: number; // USD per million input tokens
output: number; // USD per million output tokens
inputCached?: number; // cache reads
inputCacheWrite?: number; // cache writes (5-minute TTL)
inputCacheWrite1h?: number; // cache writes with the 1-hour TTL (Anthropic)
outputCached?: number;
}
interface ModelCapabilities {
supportsVision?: boolean;
supportsTools?: boolean;
supportsFunctions?: boolean;
supportsStreaming?: boolean;
supportsJson?: boolean;
}Example Model
const model = getModel('gpt-6.1-sol');
// {
// id: 'gpt-6.1-sol',
// provider: 'openai',
// displayName: 'GPT-6.1 Sol',
// pricing: { input: 2, output: 10, inputCached: 0.1 },
// contextWindow: 1050000,
// maxOutputTokens: 128000,
// capabilities: {
// supportsTools: true,
// supportsVision: true,
// supportsFunctions: true,
// supportsStreaming: true,
// supportsJson: true,
// }
// }Values shown are the built-in fallback entry; after initializeModels() the data may come from LiteLLM instead.
Filtering Models
Use ModelFilter to query specific models:
interface ModelFilter {
provider?: string;
supportsTools?: boolean;
supportsVision?: boolean;
minContextWindow?: number;
maxPricePerMillion?: number;
excludeDeprecated?: boolean;
}supportsTools and supportsVision accept both true and false (a model without the capability flag counts as false). maxPricePerMillion compares the average of the input and output price.
Filter Examples
const openaiModels = listModels({
provider: 'openai',
});
const visionModels = listModels({
supportsVision: true,
});
const toolModels = listModels({
supportsTools: true,
excludeDeprecated: true,
});
const largeContext = listModels({
minContextWindow: 100000,
});
const cheapModels = listModels({
maxPricePerMillion: 1.0,
});
const anthropicVision = listModels({
provider: 'anthropic',
supportsVision: true,
supportsTools: true,
});Providers
Built-in Providers
import { BUILTIN_PROVIDERS } from '@cogitator-ai/models';| Provider | Website | | ------------ | ---------------------- | | OpenAI | openai.com | | Anthropic | anthropic.com | | Google | ai.google.dev | | Ollama | ollama.com | | Azure OpenAI | azure.microsoft.com | | AWS Bedrock | aws.amazon.com/bedrock | | Mistral AI | mistral.ai | | Cohere | cohere.com | | Groq | groq.com | | Together AI | together.ai | | Fireworks AI | fireworks.ai | | DeepInfra | deepinfra.com | | Perplexity | perplexity.ai | | Replicate | replicate.com | | xAI | x.ai | | OpenRouter | openrouter.ai |
Provider Information
interface ProviderInfo {
id: string;
name: string;
website?: string;
models: string[];
}
const providers = registry.listProviders();
const openai = registry.getProvider('openai');
console.log(openai?.models.length);Built-in Models
The built-in models carry display names and aliases, and are part of the offline catalogue:
import {
BUILTIN_MODELS,
OPENAI_MODELS,
ANTHROPIC_MODELS,
GOOGLE_MODELS,
OPENROUTER_MODELS,
} from '@cogitator-ai/models';OPENROUTER_MODELS has the models only OpenRouter serves, which the LiteLLM catalogue does not describe: TypeSafe's Jev (typesafe/jev-1.13, kind: 'decision'), $0.042 per million input tokens, free output, 32 000 tokens of context.
Deprecated entries stay in the registry (with deprecated: true) so pricing and lookups keep working for existing configs; use excludeDeprecated: true to hide them.
OpenAI Models
- gpt-6-astra, gpt-6.1-sol, gpt-6-sol, gpt-6-luna
- gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna
- gpt-5.5, gpt-5.5-pro
- gpt-5.4, gpt-5.4-mini, gpt-5.4-pro
- gpt-4.1, gpt-4.1-mini
- gpt-4o, gpt-4o-mini
- Deprecated: gpt-5.4-nano, gpt-4.1-nano, o3, o3-mini, o3-pro, o4-mini, o1, o1-mini, o1-preview, gpt-4-turbo, gpt-4, gpt-3.5-turbo
Anthropic Models
- claude-fable-5-1, claude-opus-5-5, claude-sonnet-5-5
- claude-opus-5, claude-sonnet-5, claude-fable-5
- claude-opus-4-8, claude-opus-4-7, claude-opus-4-6, claude-sonnet-4-6
- claude-opus-4-5, claude-sonnet-4-5, claude-haiku-4-5
- Deprecated: claude-opus-4-1, claude-sonnet-4, claude-opus-4, claude-3-7-sonnet, claude-3-5-sonnet, claude-3-5-haiku, claude-3-opus, claude-3-haiku
Google Models
- gemini-3.8-flash, gemini-3.5-flash-lite, gemini-3.5-flash
- gemini-3.1-pro-preview, gemini-3-flash-preview, gemini-3.1-flash-lite
- Deprecated: gemini-3.1-flash-lite-preview, gemini-3-pro-preview, gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite, gemini-2.0-flash, gemini-2.0-flash-lite, gemini-1.5-pro, gemini-1.5-flash, gemini-1.5-flash-8b
Aliases resolve to the canonical id, for example gemini-flash / gemini-3-flash → gemini-3.8-flash, gemini-flash-lite / gemini-3-flash-lite → gemini-3.5-flash-lite, claude-sonnet-5.5 → claude-sonnet-5-5.
Caching
Memory Cache
const registry = new ModelRegistry({
cache: {
ttl: 60 * 60 * 1000,
storage: 'memory',
},
});File Cache
const registry = new ModelRegistry({
cache: {
ttl: 24 * 60 * 60 * 1000,
storage: 'file',
filePath: './cache/models.json',
},
});ModelCache Class
import { BUILTIN_MODELS, ModelCache } from '@cogitator-ai/models';
const cache = new ModelCache({
ttl: 3600000,
storage: 'file',
filePath: './models-cache.json',
});
const models = await cache.get(); // null when missing or older than ttl
await cache.set(BUILTIN_MODELS);
const staleData = await cache.getStale(); // ignores the TTL
await cache.clear();Data Fetching
LiteLLM Integration
import { fetchLiteLLMData, transformLiteLLMData } from '@cogitator-ai/models';
const rawData = await fetchLiteLLMData();
const models = transformLiteLLMData(rawData);OpenRouter Integration
import { fetchOpenRouterData, transformOpenRouterData } from '@cogitator-ai/models';
const entries = await fetchOpenRouterData(); // every page of GET /api/v1/models, checked with OpenRouterModelEntrySchema
const models = transformOpenRouterData(entries); // provider 'openrouter', catalogId 'openrouter/<id>'Routers whose price depends on the model they pick (openrouter/auto, a price of -1) carry priceUnknown: true, models past their expiration_date are deprecated, and models that do not answer in text are left out. Time and prompt-size price overrides are not applied, the base price is used.
LiteLLM Data Structure
interface LiteLLMModelEntry {
max_tokens?: number;
max_input_tokens?: number;
max_output_tokens?: number;
input_cost_per_token?: number;
output_cost_per_token?: number;
cache_read_input_token_cost?: number;
cache_creation_input_token_cost?: number;
litellm_provider?: string;
mode?: string;
supports_function_calling?: boolean;
supports_vision?: boolean;
supports_response_schema?: boolean;
supports_tool_choice?: boolean;
deprecation_date?: string;
// ...and a few more LiteLLM fields
}Examples
Cost Calculator
import { calculateCost } from '@cogitator-ai/models';
const cost = calculateCost('gpt-6.1-sol', { inputTokens: 10_000, outputTokens: 2_000 });
console.log(`Cost: $${cost?.toFixed(4)}`);Model Selector
import { listModels } from '@cogitator-ai/models';
function selectBestModel(options: {
needsVision?: boolean;
needsTools?: boolean;
maxCost?: number;
minContext?: number;
}): string | null {
const models = listModels({
supportsVision: options.needsVision,
supportsTools: options.needsTools,
maxPricePerMillion: options.maxCost,
minContextWindow: options.minContext,
excludeDeprecated: true,
});
if (models.length === 0) return null;
models.sort((a, b) => {
const aPrice = (a.pricing.input + a.pricing.output) / 2;
const bPrice = (b.pricing.input + b.pricing.output) / 2;
return aPrice - bPrice;
});
return models[0].id;
}
const cheapTool = selectBestModel({
needsTools: true,
maxCost: 2.0,
});Provider Dashboard
import { getModelRegistry, initializeModels } from '@cogitator-ai/models';
async function showDashboard() {
await initializeModels();
const registry = getModelRegistry();
console.log(`Total models: ${registry.getModelCount()}`);
console.log();
for (const provider of registry.listProviders()) {
const models = registry.listModels({ provider: provider.id });
console.log(`${provider.name}: ${models.length} models`);
const avgPrice =
models.reduce((sum, m) => sum + (m.pricing.input + m.pricing.output) / 2, 0) / models.length;
console.log(` Avg price: $${avgPrice.toFixed(2)}/M tokens`);
}
}Type Reference
import type {
ModelInfo,
ModelPricing,
ModelCapabilities,
ModelFilter,
ProviderInfo,
CacheOptions,
RegistryOptions,
LiteLLMModelEntry,
LiteLLMModelData,
TokenUsageForCost,
} from '@cogitator-ai/models';
import {
ModelInfoSchema,
ModelPricingSchema,
ModelCapabilitiesSchema,
ProviderInfoSchema,
} from '@cogitator-ai/models';License
MIT
