Releases: sethbang/open-model-selector
Releases · sethbang/open-model-selector
v0.3.0 — Audit fixes + type filter chip
Added
- In-menu type filter chip — when the catalog contains 2+ model types and the
typeprop is unset, a "Filter" chip appears next to the sort button. Click to narrow the list to one type. New props:typeFilter,onTypeFilterChange,showTypeFilter. - Strict coercion helpers —
toBool,toStr,toStrArrayalongsidetoNum. Used internally to replace unsafeascasts throughout the normalizers; exported from both"open-model-selector"and"open-model-selector/utils". - Venice
supportsMultipleImages— added toTextCapabilities; extracted frommodel_spec.capabilities. - Lint + format + pre-commit tooling — ESLint 9 (flat config, typescript-eslint + react-hooks + jsx-a11y), Prettier, Husky + lint-staged.
npm run lintis a new CI gate.
Fixed
- OpenRouter cache pricing no longer silently dropped —
normalizeTextModelnow reads OpenRouter'sinput_cache_readandinput_cache_writefields in addition tocache_input/cache_write. - Capability fields reject non-boolean shapes — an API response like
{ supportsVision: { enabled: true } }now resolves toundefinedinstead of being cast totrue. - Sort button ARIA —
aria-labeldescribes the current sort state (not the next action);aria-pressedreflects whether "newest" is active. - Pill contrast — capability pills (vision/reasoning/code/etc.) now meet WCAG AA in both light and dark mode.
- Dark-mode tooltip portal —
.oms-hover-contentgets explicit dark-mode overrides (background/border/shadow) in both media-query and class-based dark mode. - Reduced-motion spinner — the loading spinner pulses instead of rotating when
prefers-reduced-motion: reduce.
Changed
- BREAKING:
TextModel.context_lengthis nownumber | undefined— previously the field defaulted to0when no context-length source resolved, which was indistinguishable from a model with genuinely zero context. Nowundefinedsignals "unknown". Consumers doingif (model.context_length > 0)are unaffected; anything branching on=== 0will need to handleundefined. - Icons in
icons.tsxdefault toaria-hidden="true"(overridable via props). <ModelSelector>warns in dev mode when rendered with neithermodelsnorbaseUrl.nowreference for deprecation filtering is captured once at mount (was refreshed on every popover open — caused memo cascades).handleModelSelectreads from a live ref so its identity is stable across favorite toggles.ModelItem's custom memo comparator works again.- tsup no longer emits a no-op
"use client"banner on the CJS build (ESM still gets it; esbuild places the CJS banner after the runtime preamble where it has no effect). - CI runs
npm run test:coverage— thresholds (70% statements/functions/lines, 65% branches) now actually gate PRs. Baseline: 95.79 / 90.36 / 96.09 / 98.3.
v0.2.0
[0.2.0] — 2026-02-20
Added
- Multi-provider type resolution —
defaultModelNormalizernow uses a 6-tier classification strategy to correctly identify model types across providers that use non-standard vocabulary:- Direct match against canonical types (Venice)
- Non-text alias mapping (Together AI:
"audio"→"tts","transcribe"→"asr") - Architecture-based inference from
output_modalities(OpenRouter) - Heuristic inference from model ID patterns
- Text-aliased type (Together AI:
"chat"→"text", Mistral:"base"→"text", Vercel:"language"→"text") - Default to
"text"
TYPE_ALIASESexport — maps provider-specific type strings (chat,language,base,moderation,rerank,audio,transcribe) to canonicalModelTypevalues; exported from both"open-model-selector"and"open-model-selector/utils"so consumers can inspect or extend- Extended
MODEL_ID_TYPE_PATTERNS— new heuristic patterns:embedqa,imagen,gpt-image,image-preview,kling - Provider compatibility test suite (
provider-compat.test.ts) — 418 lines covering type resolution for Together AI, Vercel AI Gateway, Mistral AI, OpenRouter, OpenAI, Nvidia NIM, Helicone, and Venice AI response shapes, plusdefaultResponseExtractorwrapper shapes - Live provider Storybook stories (
provider-live.stories.tsx) — interactive stories for testing against 12 real provider APIs: OpenAI, OpenRouter, Together, Groq, Cerebras, Nvidia, Mistral, DeepSeek, SambaNova, Venice, Helicone, and Vercel .env.example— environment variable template for 13 API provider keys used by Storybook live stories and the snapshot capture script- Provider snapshot capture script (
scripts/capture-provider-snapshots.cjs) — fetches and stores real provider API responses for test fixture generation .envparser utility (scripts/parse-env.cjs) — shared.envfile parser used by both Storybook config and the snapshot capture script- Storybook provider proxy support —
.storybook/main.tsnow loads.envkeys and configures Vite proxies for cross-origin provider API access during development
v0.1.0
[0.1.0] — 2026-02-13
Initial release of open-model-selector.
Added
<ModelSelector>component — accessible combobox for selecting AI models, built with cmdk and Radix Popover- Managed mode: fetches models from any OpenAI-compatible
/v1/modelsendpoint - Controlled mode: accepts a static
modelsarray - Fuzzy search by name, provider, ID, and description
- Favorites with localStorage persistence (uncontrolled) or external state (controlled via
onToggleFavorite) - Sorting by name (A-Z) or newest, controllable or uncontrolled
- "Use System Default" sentinel option (
SYSTEM_DEFAULT_VALUE) - Popover placement (
sideprop: top, bottom, left, right) - Custom
placeholdertext classNameandrefforwarding- Configurable
storageKeyfor localStorage namespace isolation showSystemDefaulttoggleonChangecallback receives the model ID and the full model object (AnyModel | null) — no secondary lookup needed
- Managed mode: fetches models from any OpenAI-compatible
useModelshook — fetches and normalizes models from an OpenAI-compatible APIbaseUrlandapiKeyprops for endpoint configuration- Custom
fetcherprop for SSR, proxies, and testing (ref-stored — no memoization needed) - Custom
responseExtractorfor non-standard response shapes - Custom
normalizerfor non-standard model shapes - AbortController cleanup on unmount
- Re-fetches on
baseUrlorapiKeychange
defaultModelNormalizer— handles OpenAI, Venice.ai, and OpenRouter response shapes- Extracts provider from slash-separated IDs or
owned_by - Resolves
context_length,context_window, and Venicemodel_spec.availableContextTokens - Normalizes pricing from
pricing,metadata.pricing,cost, and Venicemodel_spec.pricingformats - Falls back to
model_idwhenidis missing
- Extracts provider from slash-separated IDs or
defaultResponseExtractor— handles{ data: [...] },{ models: [...] }, and top-level array responsesformatPrice— formats per-token prices into per-million-tokens dollar strings with adaptive precisionformatContextLength— formats token counts into compact strings (e.g.128k,1M,1.5M)- Separate
open-model-selector/utilsentry point — pure utility functions without"use client"directive, safe for React Server Components- Normalizers:
defaultModelNormalizer,defaultResponseExtractor, per-type normalizers (normalizeTextModel,normalizeImageModel, …) - Formatters:
formatPrice,formatContextLength,formatFlatPrice,formatAudioPrice,formatDuration,formatResolutions,formatAspectRatios - Helpers:
isDeprecated - All model types re-exported for normalizer consumers
- Normalizers:
- Scoped CSS — all variables use
--oms-prefix; never pollutes:rootor host app styles- Light theme defaults
- Dark mode via
@media (prefers-color-scheme: dark)and.darkclass ancestor - Portal-safe dark mode for popover and tooltip content
- Model tooltip — hover over a model name to see description, context length, provider badge, and input/output pricing
- Comprehensive test suite — unit, component (jsdom), and Storybook (Playwright) test projects
- Storybook stories — Default, PreselectedModel, SystemDefault, CustomPlaceholder, SortByNewest, ControlledFavorites, EmptyState, LoadingState, ErrorState, PopoverTop, WideContainer, DarkMode, MinimalModels, and VeniceLive
- GitHub Actions CI — automated npm publish on GitHub release creation
- TypeScript — full type exports (33 types):
- Base:
ModelType,BaseModel,Deprecation,AnyModel - Text:
TextModel,TextPricing,TextCapabilities,TextConstraints - Image:
ImageModel,ImagePricing,ImageConstraints - Video:
VideoModel,VideoConstraints - Inpaint:
InpaintModel,InpaintPricing,InpaintConstraints - Embedding:
EmbeddingModel,EmbeddingPricing - TTS:
TtsModel,TtsPricing - ASR:
AsrModel,AsrPricing - Upscale:
UpscaleModel,UpscalePricing - Component / Hook:
ModelSelectorProps,TextModelSelectorProps,ImageModelSelectorProps,VideoModelSelectorProps,UseModelsProps,UseModelsResult,FetchFn - Normalizer:
ModelNormalizer,ResponseExtractor
- Base:
- Dual CJS/ESM output — built with tsup, sourcemaps and
.d.tsincluded