@aetheros/models
v0.2.0
Published
Model catalog, cheap/strong routing and safe fallbacks across AI providers for AetherOS and Aevron.
Readme
@aetheros/models
One place to choose models across providers: a model catalog with prices and context sizes, cheap/strong routing, and safe fallbacks when a model is busy or down.
aether models # list models, prices, context sizes, and which providers have keys
aether models pick --tier cheap # best ready model for a tier, then fallbacks
aether models pick --tier strong --needs tools --input 20000 --output 2000 --max-cost 0.10
aether models init # starter .aether/models.json (never overwrites)
aether models prices update # fetch the public price list now (the workspace does it once a week)
aether models prices # what it fills in, and where it disagrees with checked pricesCatalog
- Built in:
- Claude: every Claude model still served, with published prices (dollars per million tokens), context size and output limit, dated with when the prices were checked.
- Google, xAI and Mistral: current models with ids, prices (standard tier), context sizes and output limits copied from each provider's own pages (ai.google.dev, docs.x.ai, docs.mistral.ai), dated
pricesAsOf2026-11-01. Notes say where a price changes for long prompts or on a set date. xAI and Mistral don't publish output limits, so those are left out. - OpenAI: current models (GPT-6 Astra, GPT-6.1 Sol and Terra, GPT-6 Luna and Sol, GPT-5.6, GPT-5.5, GPT-5.4) with ids, prices (standard tier), cached-input prices (
price.cachedInput), context sizes and output limits copied from developers.openai.com, datedpricesAsOf2026-11-01. Prices are for prompts up to 272K tokens; notes give the long-prompt rate. Cost estimates count cached input (usage.cachedInputTokens, part ofinputTokens) atprice.cachedInputand the rest at the input price. - DeepSeek, Groq, Together, Z.ai: ids, context sizes and prices (with cached-input prices where published) copied from each provider's own pages, dated
pricesAsOf2026-11-04. DeepSeek's price is its peak-hour rate (off-peak is half). Moonshot Kimi (kimi-k3.1-pro,kimi-k3) and Perplexity (sonar) have ids and context but no price: it could not be verified on their own pages, so cost is unknown until you set it in.aether/models.json. OpenRouter and Azure OpenAI have no built-in rows (any model or deployment; add your own).
- Your project:
.aether/models.jsonadds other providers' models, corrects prices, or hides a model:
Providers:{"schemaVersion": 1, "pricesAsOf": "2026-11-01", "models": [ {"provider": "google", "id": "<model id>", "tier": "balanced", "contextTokens": 1000000, "price": {"input": 0, "output": 0}}, {"provider": "local", "id": "llama3.1", "tier": "cheap", "price": {"input": 0, "output": 0}, "structuredOutput": false}, {"provider": "claude", "id": "claude-fable-5-1", "disabled": true} ]}claude,openai,azure-openai,google,xai,mistral,deepseek,groq,openrouter,together,moonshot,zai,perplexity,local, plus your own named custom providers (below). Tiers:cheap,balanced,strong. Within a tier, list order is your preference. A missing price means "unknown": such models are skipped when you set a cost limit. Capabilities (tools,streaming,structuredOutput,vision) default to what the provider's adapter can do; set one tofalseto switch it off for a model.visionmeans the model reads images. Every adapter sends images now (Claude, OpenAI, Google, OpenAI-compatible). It is on by default for Claude, OpenAI, Google and xAI (every built-in model there takes image input), on for the built-in Mistral models that read images (Large 4, Ministral 3), and off for local models: set"vision": trueon a local model that reads images (for example a LLaVA build).
Prices change. Take them from each provider's price page and keep pricesAsOf current.
Dated prices
Some prices are known to change on a set day. An entry (built in or in .aether/models.json) can say so:
"priceChange": {"on": "2026-12-01", "price": {"input": 4, "output": 20}}: an announced new price. From that day (UTC) the catalog uses it on its own, so cost estimates and daily budgets follow. After the day,aether modelsasks for the entry to be confirmed on the provider's page."priceCheck": {"by": "2026-11-22", "reason": "Promotional price"}: a price that may change without an announced new one (a promotion). The price stays; from that dayaether modelsasks for it to be checked.
aether models and aether models prices list checks that are due and those coming in the next 31 days. Built in today: Gemini 4 Argon $2/$10 → $4/$20 on 1 Dec 2026; Gemini 3.8 and 3.6 Flash $0.75/$3.75 → $1.50/$7.50 on 1 Jan 2027; GPT-5.6 Sol's promotional $4/$20 checked from 22 Nov 2026. From code: priceChecks(catalog, {today, within}), loadCatalog(root, {today}).
Prices fetched automatically
No provider offers a price API, and scraping each provider's page breaks whenever a page changes. So prices are fetched from one maintained, machine-readable list instead: LiteLLM's model price list (MIT licence, updated by many contributors and used by a lot of tools).
- It only fills gaps. A model whose price, context size or output limit is unknown gets the fetched value. In
aether modelsa fetched price is marked*, withpricesAsOfset to the day it was fetched. Prices checked on a provider's own page (built in) or set in.aether/models.jsonare never replaced. - Disagreements are shown, not applied.
aether models priceslists what was filled in and where the list disagrees with a checked price, so a person can check the provider's page. - Output limits: an output limit as large as the context is the list's placeholder, not a published limit, so it is skipped.
- When:
aether models prices updatefetches now.aether workspacerefreshes in the background when the saved list is over a week old, so budgets can count models without a built-in price.--no-price-refreshorAETHER_PRICES=offturns that off. A failed fetch keeps the previous list. - Where:
.aether/local/models-prices.json, per machine and git-ignored.loadCatalog(root, {fetched: null})ignores it. - From code:
refreshPrices(root),fetchPrices(),applyFetchedPrices(catalog, prices).
Routing
chooseModels(catalog, {tier, providers, needs, inputTokens, outputTokens, maxCostUsd}) returns models best first: the asked tier, then the nearest tiers. It leaves out models that lack a needed feature (tools, streaming, structured output), whose context is too small, or that cost more than the limit. availableProviders() lists providers whose keys are set.
Fallbacks
withFallbacks([{id, provider}, ...]) is one provider made of several. When a model fails with a "try elsewhere" error (rate limit, overload, server error, timeout, network) the next one answers. It never falls back:
- after a tool was called (the call may already have changed something),
- after reply text was shown to the person,
- after the request was cancelled,
- on a refusal (that is an answer, not a failure),
- to a model that can't do what the request needs (for example tools).
It plugs into Aevron like any provider. Aevron itself still never retries or switches providers on its own.
Providers
createProvider(provider, model) builds a client from environment keys:
| Provider | Package | Keys |
|---|---|---|
| claude | @aetheros/provider-claude | ANTHROPIC_API_KEY (or an ant auth login profile) |
| openai | @aetheros/provider-openai | OPENAI_API_KEY |
| google | @aetheros/provider-google | GEMINI_API_KEY (or GOOGLE_API_KEY) |
| azure-openai | @aetheros/provider-openai | AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT (https://<resource>.openai.azure.com), optional AZURE_OPENAI_API_VERSION; the model is the deployment name (or AZURE_OPENAI_DEPLOYMENT) |
| xai, mistral | @aetheros/provider-openai-compatible | XAI_API_KEY, MISTRAL_API_KEY |
| deepseek, groq, openrouter, together, moonshot, zai, perplexity | @aetheros/provider-openai-compatible | DEEPSEEK_API_KEY, GROQ_API_KEY, OPENROUTER_API_KEY, TOGETHER_API_KEY, MOONSHOT_API_KEY, ZAI_API_KEY, PERPLEXITY_API_KEY (addresses and default models in that package's README) |
| local | @aetheros/provider-openai-compatible | LOCAL_MODEL, optional LOCAL_MODEL_URL (default Ollama at http://127.0.0.1:11434/v1) |
openai also reads OPENAI_BASE_URL (a company proxy or other Responses-compatible address; https only, plain http only for this machine).
Every adapter offers structured output and all of them stream. Every adapter except Perplexity's offers tools, so an agent with tools can fall back to any other provider. For a local model without function calling, set "tools": false on its catalog entry.
Named custom providers (any OpenAI-compatible endpoint, no code): add a providers list to .aether/models.json, then models for it:
{"schemaVersion": 1,
"providers": [{"id": "acme-llm", "label": "Acme LLM", "baseUrl": "https://llm.acme.example/v1", "keyVar": "ACME_LLM_KEY", "model": "acme-large", "effort": true}],
"models": [{"provider": "acme-llm", "id": "acme-large", "tier": "strong", "price": {"input": 1, "output": 4}}]}Fields: id (lowercase, not a built-in name), label, baseUrl (https; http only for this machine; no credentials or query), keyVar (the name of the environment variable with the key, never the key; optional only on this machine), optional model / modelVar, path (default /chat/completions), tools, structuredOutput, vision, documents (PDF file parts), effort (sends reasoning_effort). createProvider('acme-llm', model, {root}) builds it; readCustomProviders(root) lists them; availableProviders(env, {customProviders}) includes the ones whose key is set.
