npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@molecule/app-ai-models

v1.2.0

Published

Framework-agnostic client + UI metadata for the AI model catalog

Readme

@molecule/app-ai-models

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

App-side AI model catalog client.

Framework-agnostic loader, types, and UI-only constants (PROVIDER_BRAND_COLORS, formatTokenCount, effort helpers). Hosts the lazy fetch of GET /ai/models. Framework bindings (e.g. useAIModels in @molecule/app-react) wrap this loader.

Quick Start

import { getClient } from '@molecule/app-http'
import { loadAIModels, partitionByDeprecation } from '@molecule/app-ai-models'

const models = await loadAIModels(getClient())
const { current, deprecated } = partitionByDeprecation(models)

Type

core

Installation

npm install @molecule/app-ai-models @molecule/app-http

API

Interfaces

AIModelCatalog

The full GET /ai/models payload: the model list plus (on servers that compute them) the per-mode default model ids for the requester's tier.

interface AIModelCatalog {
  /** The list of models available to the current session. */
  models: AppModelDefinition[]
  /** Per-mode server default model ids, when the server provides them. */
  defaults?: AppModeModelDefaults
}

AppModelDefinition

Client-visible model metadata. Mirrors every field of the server-side ModelDefinition; no field is currently hidden from authenticated clients.

interface AppModelDefinition {
  /** API model ID. */
  id: string
  /** Which AI provider serves this model. */
  provider: AIProviderID
  /** Human-readable label for picker display. */
  label: string
  /** Short description for picker display. */
  description: string
  /** Maximum input context window in tokens. */
  contextWindow: number
  /** Maximum output tokens per response. */
  maxOutputTokens: number
  /** Whether the model supports extended thinking / chain-of-thought. */
  supportsThinking: boolean
  /** Default thinking budget in tokens (only relevant when `supportsThinking` is true). */
  thinkingBudgetTokens: number
  /** Whether the thinking budget can be controlled via API params. */
  thinkingConfigurable: boolean
  /**
   * The model's OWN effort levels, ordered ascending — the exact values the
   * `/effort` command offers and that get persisted. Native-effort models list
   * their provider values (`['low', 'high', 'xhigh', 'max']`); budget models
   * list scaled-budget labels (`['4K', '8K', '16K', '32K']`); fixed-reasoning
   * models omit it. A persisted value outside the set degrades to the nearest.
   * Mirrors the server-side `ModelDefinition` field.
   */
  supportedEffortLevels?: EffortLevel[]
  /** The model's default effort value (a member of `supportedEffortLevels`). */
  defaultEffortLevel?: EffortLevel
  /**
   * Budget-configurable models only: maps each `supportedEffortLevels` label to
   * the thinking-token budget it sends. Its presence marks a model as
   * budget-driven (sends `budget_tokens`) rather than native-effort (sends the
   * level as the provider's effort param). Mirrors the server-side field.
   */
  effortBudgetTokens?: Record<string, number>
  /** Whether the model supports vision (images, documents, etc.). */
  supportsVision: boolean
  /** Whether the model supports prompt caching. */
  supportsPromptCaching: boolean
  /** Whether the model supports tool use / function calling. */
  supportsTools: boolean
  /** Optional provider-specific server tool type for web search. */
  webSearchToolType?: string
  /** Optional provider-specific server tool type for code execution. */
  codeExecutionToolType?: string
  /** Optional provider-specific server tool type for web fetch. */
  webFetchToolType?: string
  /** Whether this model is available on the free tier. */
  freeTier?: boolean
  /**
   * Regions in which this model is free-tier selectable even though the model
   * as a whole is not `freeTier` (regional hosts can price very differently).
   * Ignored when `freeTier` is true; omitted → no per-region free access.
   * Mirrors the API catalog's `ModelDefinition.freeTierRegions`.
   */
  freeTierRegions?: string[]
  /**
   * Processing regions this model can run in (arbitrary region codes; FIRST
   * entry is the model's default region). Omit for `['us']`. A single-entry
   * list pins the model to that region. Mirrors the server-side
   * `ModelDefinition.regions`.
   */
  regions?: string[]
  /**
   * Per-region price overrides in USD per MTok for regions billed differently
   * from the base rates (the native provider's list prices). A region with no
   * entry bills at the base rates; omitted cache fields fall back to the
   * region's `inputPricePerMTok`. Mirrors the server-side
   * `ModelDefinition.regionPricing`.
   */
  regionPricing?: Record<
    string,
    {
      /** Region input price per million uncached tokens in USD. */
      inputPricePerMTok: number
      /** Region output price per million tokens in USD. */
      outputPricePerMTok: number
      /** Region prompt-cache read price per million tokens in USD. */
      cacheReadPricePerMTok?: number
      /** Region prompt-cache write price per million tokens in USD. */
      cacheWritePricePerMTok?: number
    }
  >
  /** Input price per million *uncached* (fresh) input tokens in USD. */
  inputPricePerMTok: number
  /** Output price per million tokens in USD. */
  outputPricePerMTok: number
  /** Price per million prompt-cache *read* (cache-hit) input tokens in USD. */
  cacheReadPricePerMTok: number
  /** Price per million prompt-cache *write* (cache-creation) input tokens in USD. */
  cacheWritePricePerMTok: number
  /**
   * Provider peak-hour pricing: during these UTC windows every one of this
   * model's token rates bills at `multiplier ×` the listed values. Mirrors the
   * server-side `ModelDefinition.peakPricing`.
   *
   * Surfaced to the user because it is not a rounding error — DeepSeek's native
   * windows are a flat 2×, so the same conversation costs twice as much inside
   * them. A cost figure that silently ignores this tells users their allowance
   * drains at a rate it does not. The rate the picker shows is always the one
   * in force NOW ({@link modelUsageRate}), with the windows named alongside so
   * a number that changes during the day is explained rather than surprising.
   *
   * Windows are minutes-since-midnight UTC, half-open `[start, end)`; a window
   * may wrap midnight (`start > end`).
   *
   * Applies to the BASE rates only. A region with a `regionPricing` override is
   * a different host billing its own card, which generally has no time-of-day
   * component — so peak never applies there, matching `priceMultiplierAt`.
   */
  peakPricing?: {
    windows: { startMinuteUtc: number; endMinuteUtc: number }[]
    multiplier: number
  }
  /**
   * Fast-mode ("priority speed") pricing. PRESENCE marks the model as
   * fast-mode capable — the ⚡ composer toggle and the picker's `fast` badge
   * key off this field. Mirrors the server-side `ModelDefinition.fastPricing`.
   */
  fastPricing?: {
    /** Fast-mode input price per million uncached tokens in USD. */
    inputPricePerMTok: number
    /** Fast-mode output price per million tokens in USD. */
    outputPricePerMTok: number
    /** Fast-mode prompt-cache read price per million tokens in USD. */
    cacheReadPricePerMTok: number
    /** Fast-mode prompt-cache write price per million tokens in USD. */
    cacheWritePricePerMTok: number
  }
  /** Reliable knowledge cutoff date (YYYY-MM-DD). */
  knowledgeCutoff: string
  /**
   * When the model was (or will be) deprecated (YYYY-MM-DD).
   *
   * Past dates: still selectable, but the picker hides them in an "Older
   * models" section. Future dates: treated as current. Omit entirely for
   * current models.
   */
  deprecatedAt?: string
  /**
   * Whether this model is fully disabled — removed from selection and the
   * listing while remaining priceable for historical usage. Stronger than
   * {@link deprecatedAt} (which keeps the model selectable in an "Older models"
   * section): a disabled model is excluded from the free-tier / deprecation
   * partition helpers and never offered. Kept in sync with the server-side
   * `ModelDefinition.disabled`. Omit entirely for active models.
   */
  disabled?: boolean
  /**
   * The id of the newer-generation model that replaces this one, set on the
   * OLDER entry (e.g. `qwen3.7-max` carries `supersededBy: 'qwen3.8-max'`).
   *
   * Treated like {@link disabled} by the picker helpers — a superseded model is
   * never offered, so the user sees exactly one generation of each model family
   * — but it stays priceable for historical usage, and the successor id is the
   * migration target for a saved selection. A server that filters properly
   * never sends these; the helpers drop them anyway so an older API can't
   * reintroduce them. Kept in sync with the server-side
   * `ModelDefinition.supersededBy`. Omit entirely for current models.
   */
  supersededBy?: string
}

AppModeModelDefaults

The model ids the SERVER falls back to per mode/job when the user hasn't picked one — already resolved for the requester's tier. Mirrors the server's ModeModelDefaults. Lets the picker label an unset per-mode selection "Default ()" instead of a vague "default".

interface AppModeModelDefaults {
  /** Model id used in plan mode when nothing is configured. */
  plan: string
  /** Model id used in execute mode when nothing is configured. */
  execute: string
  /** Model id used for commit-message generation when nothing is configured. */
  commit: string
  /** Model id used for conversation compaction when nothing is configured. */
  compact: string
}

EffortOption

One selectable effort option for a model — its own native value.

interface EffortOption {
  /** The native value the user sees, types, and that gets persisted. */
  value: string
}

ListAIModelsResponse

Wire-shape response returned by GET /ai/models.

interface ListAIModelsResponse {
  models: AppModelDefinition[]
  /**
   * Per-mode server default model ids for the requester's tier. Optional —
   * servers that don't compute them omit it, and clients fall back to generic
   * "default" labeling.
   */
  defaults?: AppModeModelDefaults
}

Types

AIProviderID

AI provider identifier shipped with each model. Stays in sync with the server-side AIProviderID by convention; any drift here will surface as a mismatch with PROVIDER_BRAND_COLORS.

type AIProviderID =
  | 'anthropic'
  | 'openai'
  | 'google'
  | 'xai'
  | 'deepseek'
  | 'meta'
  | 'moonshot'
  | 'minimax'
  | 'alibaba'
  | 'zhipu'
  /**
   * A model served by a USER-configured endpoint + key (bring-your-own AI).
   * Appears only in project-scoped listings (`GET /ai/models?projectId=…`);
   * pricing fields are 0 (the user pays their own provider directly).
   */
  | 'custom'

EffortLevel

A reasoning-effort value — a model's OWN native effort level (e.g. 'high', 'xhigh', 'max', or a budget label like '16K'). There is no abstract cross-model scale; the stored value is the model's real level. Mirrors the server-side EffortLevel in @molecule/api-resource-ai-models; keep in sync.

type EffortLevel = string

Functions

defaultEffortForModel(model)

The model's default effort value (used when the user hasn't chosen), or null when the model has no effort levels.

function defaultEffortForModel(model: AppModelDefinition | undefined): string | null
  • model — The model (or undefined).

Returns: The default native value, or null.

effortOptionsForModel(model)

The effort options a user can pick for a model, in ascending order — the model's own supportedEffortLevels. Empty for fixed-reasoning models (and unknown models), which expose no effort choice.

function effortOptionsForModel(model: AppModelDefinition | undefined): EffortOption[]
  • model — The model to build options for, or undefined when unknown.

Returns: The selectable options (empty when the model has no effort levels).

formatTokenCount(tokens)

Format a token count for display (e.g. 200000 -> "200K", 1000000 -> "1M").

function formatTokenCount(tokens: number): string
  • tokens — Token count.

Returns: Formatted string.

isDeprecated(model, now)

Returns true when the model is deprecated as of now. A model is deprecated if deprecatedAt is set and lexicographically <= the now date (YYYY-MM-DD strings compare as dates). Models with a future deprecatedAt are still current — useful for scheduling deprecations.

function isDeprecated(model: Pick<AppModelDefinition, 'deprecatedAt'>, now?: string): boolean
  • model — Model to check.
  • now — Today's date as YYYY-MM-DD. Defaults to the current UTC date.

Returns: true if the model is deprecated as of now.

isSelectableModel(model)

Whether a model may be offered in the picker: neither disabled (retired by the provider) nor supersededBy a newer generation of its own family. The single predicate every picker helper here uses, mirroring the server-side isSelectableModel.

function isSelectableModel(model: Pick<AppModelDefinition, 'disabled' | 'supersededBy'>): boolean
  • model — Model to check (only the two flags are read).

Returns: true when the model may be listed and chosen.

loadAIModelCatalog(http, path, projectId)

Fetches the AI model catalog from the API — the model list plus the server's per-mode default model ids (when provided).

function loadAIModelCatalog(
  http: HttpClient,
  path?: string,
  projectId?: string,
): Promise<AIModelCatalog>
  • http — HTTP client bonded by the host app.
  • path — Endpoint path, defaults to '/ai/models' (the http client supplies the base URL).
  • projectId — Optional project scope. When set, servers that support per-project custom ("bring your own AI") models append them to the catalog, flagged provider: 'custom'; servers that ignore the query param return the unscoped catalog unchanged.

Returns: The catalog: models plus optional per-mode defaults.

loadAIModels(http, path, projectId)

Fetches the AI model catalog from the API and returns the models array. Thin back-compat wrapper over {@link loadAIModelCatalog} for callers that don't need the per-mode defaults.

function loadAIModels(
  http: HttpClient,
  path?: string,
  projectId?: string,
): Promise<AppModelDefinition[]>
  • http — HTTP client bonded by the host app.
  • path — Endpoint path, defaults to '/ai/models' (the http client supplies the base URL).
  • projectId — Optional project scope (see {@link loadAIModelCatalog}).

Returns: The list of models available to the current session.

nativeEffortName(model, value)

Resolve a persisted effort value to the one the model will actually use — its exact value, a legacy S|M|L|XL mapped by position, the nearest native level by rank, or the model's default. null when the model has no effort levels (fixed reasoning — callers show their own "fixed" copy). Mirrors the server-side resolveEffortForModel so the display always matches what the backend sends.

function nativeEffortName(
  model: AppModelDefinition | undefined,
  value: string | undefined,
): string | null
  • model — The active model (or undefined).
  • value — The persisted effort value (or undefined).

Returns: The resolved native value, or null when reasoning is fixed.

partitionByDeprecation(models, now)

Splits a model catalog into current and deprecated entries based on each model's deprecatedAt relative to now. Order within each partition is preserved. Models that are not {@link isSelectableModel} — disabled or superseded by a newer generation — are dropped entirely: they belong in neither partition (the listing already excludes them, and they must not surface in the picker's current or "Older models" section).

function partitionByDeprecation(
  models: readonly AppModelDefinition[],
  now?: string,
): { current: AppModelDefinition[]; deprecated: AppModelDefinition[] }
  • models — Loaded model catalog.
  • now — Today's date as YYYY-MM-DD. Defaults to the current UTC date.

Returns: Object with current and deprecated arrays.

pickFreeTierModel(models)

Returns the free-tier model from a list, or undefined if none is marked. Models that are not {@link isSelectableModel} are ignored — a retired or superseded model is never picked as the free-tier default even if it still carries the flag.

function pickFreeTierModel(models: readonly AppModelDefinition[]): AppModelDefinition | undefined
  • models — Loaded model catalog.

Returns: The single selectable model with freeTier: true, or undefined.

Constants

PROVIDER_BRAND_COLORS

Brand colors keyed by provider ID. Used as accent colors in picker rows.

const PROVIDER_BRAND_COLORS: Readonly<Record<AIProviderID, string>>

Injection Notes

Requirements

Peer dependencies:

  • @molecule/app-http ^1.0.1

Runtime Dependencies

  • @molecule/app-http

  • The server side is @molecule/api-resource-ai-models — it serves the auth-gated GET /ai/models route this loader calls. Without that resource (or an equivalent route returning ListAIModelsResponse), loadAIModels fails. The route is session-gated: fetch with the app's authenticated HTTP client.

  • loadAIModels does NOT cache — call it once and keep the result (the framework hook does this for you). Use pickFreeTierModel / partitionByDeprecation instead of re-deriving tier/deprecation logic; disabled models must never surface in a picker.