open-model-selector 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -1
- package/README.md +46 -4
- package/dist/index.cjs +44 -7
- package/dist/index.d.cts +20 -6
- package/dist/index.d.ts +20 -6
- package/dist/index.js +43 -6
- package/dist/utils.cjs +42 -5
- package/dist/utils.d.cts +20 -6
- package/dist/utils.d.ts +20 -6
- package/dist/utils.js +41 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] — 2026-02-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Multi-provider type resolution** — `defaultModelNormalizer` now uses a 6-tier classification strategy to correctly identify model types across providers that use non-standard vocabulary:
|
|
15
|
+
1. Direct match against canonical types (Venice)
|
|
16
|
+
2. Non-text alias mapping (Together AI: `"audio"`→`"tts"`, `"transcribe"`→`"asr"`)
|
|
17
|
+
3. Architecture-based inference from `output_modalities` (OpenRouter)
|
|
18
|
+
4. Heuristic inference from model ID patterns
|
|
19
|
+
5. Text-aliased type (Together AI: `"chat"`→`"text"`, Mistral: `"base"`→`"text"`, Vercel: `"language"`→`"text"`)
|
|
20
|
+
6. Default to `"text"`
|
|
21
|
+
- **`TYPE_ALIASES` export** — maps provider-specific type strings (`chat`, `language`, `base`, `moderation`, `rerank`, `audio`, `transcribe`) to canonical `ModelType` values; exported from both `"open-model-selector"` and `"open-model-selector/utils"` so consumers can inspect or extend
|
|
22
|
+
- **Extended `MODEL_ID_TYPE_PATTERNS`** — new heuristic patterns: `embedqa`, `imagen`, `gpt-image`, `image-preview`, `kling`
|
|
23
|
+
- **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, plus `defaultResponseExtractor` wrapper shapes
|
|
24
|
+
- **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
|
|
25
|
+
- **`.env.example`** — environment variable template for 13 API provider keys used by Storybook live stories and the snapshot capture script
|
|
26
|
+
- **Provider snapshot capture script** (`scripts/capture-provider-snapshots.cjs`) — fetches and stores real provider API responses for test fixture generation
|
|
27
|
+
- **`.env` parser utility** (`scripts/parse-env.cjs`) — shared `.env` file parser used by both Storybook config and the snapshot capture script
|
|
28
|
+
- **Storybook provider proxy support** — `.storybook/main.ts` now loads `.env` keys and configures Vite proxies for cross-origin provider API access during development
|
|
29
|
+
|
|
10
30
|
## [0.1.0] — 2026-02-06
|
|
11
31
|
|
|
12
32
|
Initial release of `open-model-selector`.
|
|
@@ -68,5 +88,6 @@ Initial release of `open-model-selector`.
|
|
|
68
88
|
- **Normalizer**: `ModelNormalizer`, `ResponseExtractor`
|
|
69
89
|
- **Dual CJS/ESM output** — built with tsup, sourcemaps and `.d.ts` included
|
|
70
90
|
|
|
71
|
-
[Unreleased]: https://github.com/sethbang/open-model-selector/compare/v0.
|
|
91
|
+
[Unreleased]: https://github.com/sethbang/open-model-selector/compare/v0.2.0...HEAD
|
|
92
|
+
[0.2.0]: https://github.com/sethbang/open-model-selector/compare/v0.1.0...v0.2.0
|
|
72
93
|
[0.1.0]: https://github.com/sethbang/open-model-selector/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# open-model-selector
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/open-model-selector) [](https://www.npmjs.com/package/open-model-selector) [](https://opensource.org/licenses/MIT) [](https://www.typescriptlang.org/)
|
|
4
4
|
|
|
5
5
|
### An accessible, themeable React model-selector combobox for any OpenAI-compatible API — with first-class [Venice.ai](https://venice.ai) support.
|
|
6
6
|
|
|
@@ -37,13 +37,16 @@
|
|
|
37
37
|
- **"Use System Default"** sentinel option for fallback behavior
|
|
38
38
|
- **8 model types**: text, image, video, inpaint, embedding, TTS, ASR, upscale
|
|
39
39
|
- **Specialized selectors**: `<TextModelSelector>`, `<ImageModelSelector>`, `<VideoModelSelector>`
|
|
40
|
-
- **Built-in normalizers** for Venice.ai, OpenAI, and
|
|
40
|
+
- **Built-in normalizers** for Venice.ai, OpenAI, OpenRouter, Together AI, Vercel AI Gateway, Mistral, Groq, Cerebras, Nvidia NIM, DeepSeek, SambaNova, and Helicone response shapes
|
|
41
41
|
- **Scoped CSS** with `--oms-` custom property prefix — never pollutes host styles
|
|
42
42
|
- **Dark mode** via `prefers-color-scheme` and `.dark` class
|
|
43
43
|
- **Full TypeScript types** exported
|
|
44
44
|
- **Dual CJS/ESM output** with sourcemaps
|
|
45
45
|
- **React 18 and 19** support
|
|
46
46
|
|
|
47
|
+
### Check out the [demo here!](https://sethbang.github.io/open-model-selector-demo/)
|
|
48
|
+
|
|
49
|
+
|
|
47
50
|
## Installation
|
|
48
51
|
|
|
49
52
|
### Install the package
|
|
@@ -444,7 +447,7 @@ import type { ModelNormalizer, ResponseExtractor } from "open-model-selector"
|
|
|
444
447
|
|
|
445
448
|
### Custom Normalizer
|
|
446
449
|
|
|
447
|
-
The built-in `defaultModelNormalizer` handles Venice.ai, OpenAI,
|
|
450
|
+
The built-in `defaultModelNormalizer` handles response shapes from Venice.ai, OpenAI, OpenRouter, Together AI, Vercel AI Gateway, Mistral, Groq, Cerebras, Nvidia NIM, DeepSeek, SambaNova, and Helicone automatically — using a [6-tier type classification strategy](#type-inference) that resolves provider-specific vocabulary, architecture metadata, and model ID heuristics. Venice's nested `model_spec` format with capabilities, privacy, traits, and deprecation info is fully supported. If your API returns a different shape, provide a custom normalizer:
|
|
448
451
|
|
|
449
452
|
```tsx
|
|
450
453
|
import { ModelSelector } from "open-model-selector"
|
|
@@ -631,12 +634,42 @@ const imageModel = normalizeImageModel(rawApiObject) // → ImageModel
|
|
|
631
634
|
|
|
632
635
|
#### Type Inference
|
|
633
636
|
|
|
634
|
-
`
|
|
637
|
+
`defaultModelNormalizer` uses a **6-tier type resolution strategy** to correctly classify models across providers with different metadata formats:
|
|
638
|
+
|
|
639
|
+
| Tier | Strategy | Providers |
|
|
640
|
+
|------|----------|-----------|
|
|
641
|
+
| 1 | Direct match against canonical types (`text`, `image`, etc.) | Venice |
|
|
642
|
+
| 2 | Non-text alias mapping (e.g. `"audio"`→`"tts"`, `"transcribe"`→`"asr"`) | Together AI |
|
|
643
|
+
| 3 | Architecture-based inference from `output_modalities` | OpenRouter |
|
|
644
|
+
| 4 | Heuristic pattern matching on model ID | OpenAI, Nvidia, Helicone |
|
|
645
|
+
| 5 | Text-aliased type (e.g. `"chat"`→`"text"`, `"base"`→`"text"`) | Together AI, Mistral, Vercel |
|
|
646
|
+
| 6 | Default to `"text"` | All |
|
|
647
|
+
|
|
648
|
+
> Text aliases (Tier 5) are checked **after** the ID heuristic (Tier 4) so that e.g. Mistral's `mistral-embed` (type: `"base"`) still gets classified as `"embedding"` via its model ID rather than being swallowed by the `"base"`→`"text"` alias.
|
|
649
|
+
|
|
650
|
+
**`TYPE_ALIASES`** maps provider-specific type vocabulary to canonical `ModelType` values:
|
|
651
|
+
|
|
652
|
+
```ts
|
|
653
|
+
import { TYPE_ALIASES } from "open-model-selector/utils"
|
|
654
|
+
|
|
655
|
+
// TYPE_ALIASES = {
|
|
656
|
+
// chat: 'text', // Together AI
|
|
657
|
+
// language: 'text', // Vercel AI Gateway, Together AI
|
|
658
|
+
// base: 'text', // Mistral AI
|
|
659
|
+
// moderation: 'text', // Together AI
|
|
660
|
+
// rerank: 'text', // Together AI
|
|
661
|
+
// audio: 'tts', // Together AI (Cartesia Sonic etc.)
|
|
662
|
+
// transcribe: 'asr', // Together AI (Whisper etc.)
|
|
663
|
+
// }
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
**`inferTypeFromId`** uses heuristic pattern matching on a model's ID string (Tier 4). This is how `defaultModelNormalizer` classifies models from providers that don't include an explicit `type` field (e.g., OpenAI, Nvidia):
|
|
635
667
|
|
|
636
668
|
```ts
|
|
637
669
|
import { inferTypeFromId, MODEL_ID_TYPE_PATTERNS } from "open-model-selector/utils"
|
|
638
670
|
|
|
639
671
|
inferTypeFromId("dall-e-3") // "image"
|
|
672
|
+
inferTypeFromId("gpt-image-1") // "image"
|
|
640
673
|
inferTypeFromId("whisper-large-v3") // "asr"
|
|
641
674
|
inferTypeFromId("gpt-4o") // undefined (no match → caller defaults to "text")
|
|
642
675
|
```
|
|
@@ -763,6 +796,7 @@ The component implements the [ARIA combobox pattern](https://www.w3.org/WAI/ARIA
|
|
|
763
796
|
| `npm run test:watch` | Run tests in watch mode. |
|
|
764
797
|
| `npm run storybook` | Start Storybook dev server on port 6006. |
|
|
765
798
|
| `npm run build-storybook` | Build static Storybook site. |
|
|
799
|
+
| `node scripts/capture-provider-snapshots.cjs` | Capture real provider API responses into `test-fixtures/providers/` for test fixture generation. Requires `.env` with API keys. |
|
|
766
800
|
|
|
767
801
|
> `npm run prepublishOnly` runs typecheck → test → build automatically before publishing.
|
|
768
802
|
|
|
@@ -780,6 +814,7 @@ Test files:
|
|
|
780
814
|
- [`src/hooks/use-models.test.tsx`](src/hooks/use-models.test.tsx) — Hook tests
|
|
781
815
|
- [`src/utils/format.test.ts`](src/utils/format.test.ts) — Format utility tests
|
|
782
816
|
- [`src/utils/normalizers/normalizers.test.ts`](src/utils/normalizers/normalizers.test.ts) — Normalizer tests
|
|
817
|
+
- [`src/utils/normalizers/provider-compat.test.ts`](src/utils/normalizers/provider-compat.test.ts) — Provider compatibility tests (Together AI, Vercel, Mistral, OpenRouter, OpenAI, Nvidia, Helicone, Venice)
|
|
783
818
|
|
|
784
819
|
```bash
|
|
785
820
|
# Run all tests
|
|
@@ -793,7 +828,14 @@ npm run test:watch
|
|
|
793
828
|
|
|
794
829
|
The project includes comprehensive Storybook stories covering multiple scenarios: Default, PreselectedModel, SystemDefault, CustomPlaceholder, SortByNewest, ControlledFavorites, EmptyState, LoadingState, ErrorState, PopoverTop, WideContainer, DarkMode, MinimalModels, and VeniceLive.
|
|
795
830
|
|
|
831
|
+
**Live provider stories** are also available for testing against real APIs (OpenAI, OpenRouter, Together, Groq, Cerebras, Nvidia, Mistral, DeepSeek, SambaNova, Venice, Helicone, Vercel). To use them:
|
|
832
|
+
|
|
833
|
+
1. Copy `.env.example` to `.env` and fill in the API keys you want to test
|
|
834
|
+
2. Run `npm run storybook` — the Storybook config auto-loads your `.env` keys and configures CORS proxies
|
|
835
|
+
|
|
796
836
|
```bash
|
|
837
|
+
cp .env.example .env
|
|
838
|
+
# Edit .env with your API keys
|
|
797
839
|
npm run storybook
|
|
798
840
|
# Opens at http://localhost:6006
|
|
799
841
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -10,11 +10,12 @@ var _reactpopover = require('@radix-ui/react-popover'); var PopoverPrimitive = _
|
|
|
10
10
|
|
|
11
11
|
// src/utils/normalizers/type-inference.ts
|
|
12
12
|
var MODEL_ID_TYPE_PATTERNS = [
|
|
13
|
-
[/\b(embed|embedding)\b/i, "embedding"],
|
|
14
|
-
[/\b(dall-e|stable-diffusion|sdxl|midjourney|flux)\b/i, "image"],
|
|
13
|
+
[/\b(embed|embedding|embedqa)\b/i, "embedding"],
|
|
14
|
+
[/\b(dall-e|stable-diffusion|sdxl|midjourney|flux|imagen)\b/i, "image"],
|
|
15
|
+
[/\b(gpt-image|image-preview)\b/i, "image"],
|
|
15
16
|
[/\b(tts)\b/i, "tts"],
|
|
16
17
|
[/\b(whisper|asr)\b/i, "asr"],
|
|
17
|
-
[/\b(sora|video|wan)\b/i, "video"],
|
|
18
|
+
[/\b(sora|video|wan|kling)\b/i, "video"],
|
|
18
19
|
[/\b(inpaint)\b/i, "inpaint"],
|
|
19
20
|
[/\b(upscale|esrgan)\b/i, "upscale"]
|
|
20
21
|
];
|
|
@@ -277,10 +278,45 @@ function defaultResponseExtractor(body) {
|
|
|
277
278
|
return [];
|
|
278
279
|
}
|
|
279
280
|
var VALID_TYPES = /* @__PURE__ */ new Set(["text", "image", "video", "inpaint", "embedding", "tts", "asr", "upscale"]);
|
|
281
|
+
var TYPE_ALIASES = {
|
|
282
|
+
// Together AI
|
|
283
|
+
chat: "text",
|
|
284
|
+
language: "text",
|
|
285
|
+
// Vercel AI Gateway, Together AI
|
|
286
|
+
base: "text",
|
|
287
|
+
// Mistral AI
|
|
288
|
+
moderation: "text",
|
|
289
|
+
// Together AI
|
|
290
|
+
rerank: "text",
|
|
291
|
+
// Together AI
|
|
292
|
+
audio: "tts",
|
|
293
|
+
// Together AI (Cartesia Sonic etc.)
|
|
294
|
+
transcribe: "asr"
|
|
295
|
+
// Together AI (Whisper etc.)
|
|
296
|
+
// image, video, embedding already match VALID_TYPES directly
|
|
297
|
+
};
|
|
298
|
+
function inferTypeFromArchitecture(raw) {
|
|
299
|
+
const arch = raw.architecture;
|
|
300
|
+
if (!arch) return void 0;
|
|
301
|
+
const outputMods = arch.output_modalities;
|
|
302
|
+
if (!Array.isArray(outputMods) || outputMods.length === 0) return void 0;
|
|
303
|
+
if (outputMods.includes("image") && !outputMods.includes("text")) return "image";
|
|
304
|
+
if (outputMods.length === 1 && outputMods[0] === "audio") return "tts";
|
|
305
|
+
return void 0;
|
|
306
|
+
}
|
|
280
307
|
function defaultModelNormalizer(raw) {
|
|
281
308
|
const id = _nullishCoalesce(_nullishCoalesce(raw.id, () => ( raw.model_id)), () => ( ""));
|
|
282
309
|
const rawType = raw.type;
|
|
283
|
-
const
|
|
310
|
+
const aliasedType = rawType && rawType in TYPE_ALIASES ? TYPE_ALIASES[rawType] : void 0;
|
|
311
|
+
const type = (
|
|
312
|
+
// 1. Direct match against canonical types (Venice)
|
|
313
|
+
_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce((rawType && VALID_TYPES.has(rawType) ? rawType : void 0), () => ( // 2. Non-text alias (e.g. "audio"→"tts", "transcribe"→"asr") — these are specific enough to trust
|
|
314
|
+
(aliasedType && aliasedType !== "text" ? aliasedType : void 0))), () => ( // 3. Architecture-based inference (OpenRouter output_modalities)
|
|
315
|
+
inferTypeFromArchitecture(raw))), () => ( // 4. Heuristic from model ID patterns
|
|
316
|
+
inferTypeFromId(id))), () => ( // 5. Text-aliased type (e.g. "chat"→"text", "base"→"text", "language"→"text")
|
|
317
|
+
aliasedType)), () => ( // 6. Default to 'text'
|
|
318
|
+
"text"))
|
|
319
|
+
);
|
|
284
320
|
switch (type) {
|
|
285
321
|
case "text":
|
|
286
322
|
return normalizeTextModel(raw);
|
|
@@ -325,10 +361,10 @@ function useModels(props) {
|
|
|
325
361
|
setError(null);
|
|
326
362
|
return;
|
|
327
363
|
}
|
|
328
|
-
if (!/^https
|
|
364
|
+
if (!/^(https?:\/\/|\/(?!\/))/i.test(baseUrl)) {
|
|
329
365
|
setAllModels([]);
|
|
330
366
|
setLoading(false);
|
|
331
|
-
setError(new Error(`Invalid baseUrl scheme: URL must start with http
|
|
367
|
+
setError(new Error(`Invalid baseUrl scheme: URL must start with http://, https://, or /`));
|
|
332
368
|
return;
|
|
333
369
|
}
|
|
334
370
|
const controller = new AbortController();
|
|
@@ -1370,4 +1406,5 @@ VideoModelSelector.displayName = "VideoModelSelector";
|
|
|
1370
1406
|
|
|
1371
1407
|
|
|
1372
1408
|
|
|
1373
|
-
|
|
1409
|
+
|
|
1410
|
+
exports.ImageModelSelector = ImageModelSelector; exports.MODEL_ID_TYPE_PATTERNS = MODEL_ID_TYPE_PATTERNS; exports.ModelSelector = ModelSelector; exports.SYSTEM_DEFAULT_VALUE = SYSTEM_DEFAULT_VALUE; exports.TYPE_ALIASES = TYPE_ALIASES; exports.TextModelSelector = TextModelSelector; exports.VideoModelSelector = VideoModelSelector; exports.defaultModelNormalizer = defaultModelNormalizer; exports.defaultResponseExtractor = defaultResponseExtractor; exports.extractBaseFields = extractBaseFields; exports.formatAspectRatios = formatAspectRatios; exports.formatAudioPrice = formatAudioPrice; exports.formatContextLength = formatContextLength; exports.formatDuration = formatDuration; exports.formatFlatPrice = formatFlatPrice; exports.formatPrice = formatPrice; exports.formatResolutions = formatResolutions; exports.inferTypeFromId = inferTypeFromId; exports.isDeprecated = isDeprecated; exports.normalizeAsrModel = normalizeAsrModel; exports.normalizeEmbeddingModel = normalizeEmbeddingModel; exports.normalizeImageModel = normalizeImageModel; exports.normalizeInpaintModel = normalizeInpaintModel; exports.normalizeTextModel = normalizeTextModel; exports.normalizeTtsModel = normalizeTtsModel; exports.normalizeUpscaleModel = normalizeUpscaleModel; exports.normalizeVideoModel = normalizeVideoModel; exports.toNum = toNum; exports.useModels = useModels;
|
package/dist/index.d.cts
CHANGED
|
@@ -177,7 +177,10 @@ declare function extractBaseFields(raw: Record<string, unknown>): Omit<BaseModel
|
|
|
177
177
|
|
|
178
178
|
/** Known model ID patterns for heuristic type inference.
|
|
179
179
|
* Used when the API response lacks an explicit `type` field (non-Venice providers).
|
|
180
|
-
* Exported so consumers can inspect or extend.
|
|
180
|
+
* Exported so consumers can inspect or extend.
|
|
181
|
+
*
|
|
182
|
+
* Patterns are tested in order — first match wins. More specific patterns should
|
|
183
|
+
* come before broader ones (e.g. "gpt-image" before generic "image"). */
|
|
181
184
|
declare const MODEL_ID_TYPE_PATTERNS: Array<[RegExp, ModelType]>;
|
|
182
185
|
/** Infer model type from its ID using naming conventions.
|
|
183
186
|
* Returns undefined if no pattern matches (caller should fall back to 'text'). */
|
|
@@ -219,11 +222,22 @@ type ModelNormalizer = (raw: Record<string, unknown>) => AnyModel;
|
|
|
219
222
|
* - `{ models: [...] }` → return models
|
|
220
223
|
* - Fallback → empty array */
|
|
221
224
|
declare function defaultResponseExtractor(body: Record<string, unknown> | unknown[]): Record<string, unknown>[];
|
|
225
|
+
/** Maps provider-specific type strings to our canonical ModelType values.
|
|
226
|
+
* Covers vocabulary differences across Together AI, Vercel AI Gateway, Mistral, etc.
|
|
227
|
+
* Exported so consumers can inspect or extend. */
|
|
228
|
+
declare const TYPE_ALIASES: Record<string, ModelType>;
|
|
222
229
|
/** Default dispatching model normalizer.
|
|
223
|
-
* Uses
|
|
224
|
-
* 1. Explicit `raw.type` field (Venice, custom providers)
|
|
225
|
-
* 2.
|
|
226
|
-
* 3.
|
|
230
|
+
* Uses multi-tier type resolution:
|
|
231
|
+
* 1. Explicit `raw.type` field matching our canonical types (Venice, custom providers)
|
|
232
|
+
* 2. Non-text alias mapping for provider-specific vocabulary (Together AI: "audio"→"tts")
|
|
233
|
+
* 3. Architecture-based inference from `output_modalities` (OpenRouter)
|
|
234
|
+
* 4. Heuristic inference from model ID patterns (OpenAI, Nvidia, etc.)
|
|
235
|
+
* 5. Text-aliased type (Together AI: "chat"→"text", Mistral: "base"→"text")
|
|
236
|
+
* 6. Fallback to 'text' — safe default for most providers
|
|
237
|
+
*
|
|
238
|
+
* Note: aliases that resolve to 'text' are checked AFTER the ID heuristic (Tier 4)
|
|
239
|
+
* so that e.g. Mistral's `mistral-embed` (type: "base") still gets classified as
|
|
240
|
+
* 'embedding' via its model ID rather than being swallowed by the "base"→"text" alias. */
|
|
227
241
|
declare function defaultModelNormalizer(raw: Record<string, unknown>): AnyModel;
|
|
228
242
|
|
|
229
243
|
/** A fetch-compatible function signature. Used for SSR, testing, or proxy scenarios. */
|
|
@@ -466,4 +480,4 @@ type ImageModelSelectorProps = Omit<ModelSelectorProps, 'type'>;
|
|
|
466
480
|
/** Props for VideoModelSelector — same as ModelSelectorProps but without `type`. */
|
|
467
481
|
type VideoModelSelectorProps = Omit<ModelSelectorProps, 'type'>;
|
|
468
482
|
|
|
469
|
-
export { type AnyModel, type AsrModel, type AsrPricing, type BaseModel, type Deprecation, type EmbeddingModel, type EmbeddingPricing, type FetchFn, type ImageConstraints, type ImageModel, ImageModelSelector, type ImageModelSelectorProps, type ImagePricing, type InpaintConstraints, type InpaintModel, type InpaintPricing, MODEL_ID_TYPE_PATTERNS, type ModelNormalizer, ModelSelector, type ModelSelectorProps, type ModelType, type ResponseExtractor, SYSTEM_DEFAULT_VALUE, type TextCapabilities, type TextConstraints, type TextModel, TextModelSelector, type TextModelSelectorProps, type TextPricing, type TtsModel, type TtsPricing, type UpscaleModel, type UpscalePricing, type UseModelsProps, type UseModelsResult, type VideoConstraints, type VideoModel, VideoModelSelector, type VideoModelSelectorProps, defaultModelNormalizer, defaultResponseExtractor, extractBaseFields, formatAspectRatios, formatAudioPrice, formatContextLength, formatDuration, formatFlatPrice, formatPrice, formatResolutions, inferTypeFromId, isDeprecated, normalizeAsrModel, normalizeEmbeddingModel, normalizeImageModel, normalizeInpaintModel, normalizeTextModel, normalizeTtsModel, normalizeUpscaleModel, normalizeVideoModel, toNum, useModels };
|
|
483
|
+
export { type AnyModel, type AsrModel, type AsrPricing, type BaseModel, type Deprecation, type EmbeddingModel, type EmbeddingPricing, type FetchFn, type ImageConstraints, type ImageModel, ImageModelSelector, type ImageModelSelectorProps, type ImagePricing, type InpaintConstraints, type InpaintModel, type InpaintPricing, MODEL_ID_TYPE_PATTERNS, type ModelNormalizer, ModelSelector, type ModelSelectorProps, type ModelType, type ResponseExtractor, SYSTEM_DEFAULT_VALUE, TYPE_ALIASES, type TextCapabilities, type TextConstraints, type TextModel, TextModelSelector, type TextModelSelectorProps, type TextPricing, type TtsModel, type TtsPricing, type UpscaleModel, type UpscalePricing, type UseModelsProps, type UseModelsResult, type VideoConstraints, type VideoModel, VideoModelSelector, type VideoModelSelectorProps, defaultModelNormalizer, defaultResponseExtractor, extractBaseFields, formatAspectRatios, formatAudioPrice, formatContextLength, formatDuration, formatFlatPrice, formatPrice, formatResolutions, inferTypeFromId, isDeprecated, normalizeAsrModel, normalizeEmbeddingModel, normalizeImageModel, normalizeInpaintModel, normalizeTextModel, normalizeTtsModel, normalizeUpscaleModel, normalizeVideoModel, toNum, useModels };
|
package/dist/index.d.ts
CHANGED
|
@@ -177,7 +177,10 @@ declare function extractBaseFields(raw: Record<string, unknown>): Omit<BaseModel
|
|
|
177
177
|
|
|
178
178
|
/** Known model ID patterns for heuristic type inference.
|
|
179
179
|
* Used when the API response lacks an explicit `type` field (non-Venice providers).
|
|
180
|
-
* Exported so consumers can inspect or extend.
|
|
180
|
+
* Exported so consumers can inspect or extend.
|
|
181
|
+
*
|
|
182
|
+
* Patterns are tested in order — first match wins. More specific patterns should
|
|
183
|
+
* come before broader ones (e.g. "gpt-image" before generic "image"). */
|
|
181
184
|
declare const MODEL_ID_TYPE_PATTERNS: Array<[RegExp, ModelType]>;
|
|
182
185
|
/** Infer model type from its ID using naming conventions.
|
|
183
186
|
* Returns undefined if no pattern matches (caller should fall back to 'text'). */
|
|
@@ -219,11 +222,22 @@ type ModelNormalizer = (raw: Record<string, unknown>) => AnyModel;
|
|
|
219
222
|
* - `{ models: [...] }` → return models
|
|
220
223
|
* - Fallback → empty array */
|
|
221
224
|
declare function defaultResponseExtractor(body: Record<string, unknown> | unknown[]): Record<string, unknown>[];
|
|
225
|
+
/** Maps provider-specific type strings to our canonical ModelType values.
|
|
226
|
+
* Covers vocabulary differences across Together AI, Vercel AI Gateway, Mistral, etc.
|
|
227
|
+
* Exported so consumers can inspect or extend. */
|
|
228
|
+
declare const TYPE_ALIASES: Record<string, ModelType>;
|
|
222
229
|
/** Default dispatching model normalizer.
|
|
223
|
-
* Uses
|
|
224
|
-
* 1. Explicit `raw.type` field (Venice, custom providers)
|
|
225
|
-
* 2.
|
|
226
|
-
* 3.
|
|
230
|
+
* Uses multi-tier type resolution:
|
|
231
|
+
* 1. Explicit `raw.type` field matching our canonical types (Venice, custom providers)
|
|
232
|
+
* 2. Non-text alias mapping for provider-specific vocabulary (Together AI: "audio"→"tts")
|
|
233
|
+
* 3. Architecture-based inference from `output_modalities` (OpenRouter)
|
|
234
|
+
* 4. Heuristic inference from model ID patterns (OpenAI, Nvidia, etc.)
|
|
235
|
+
* 5. Text-aliased type (Together AI: "chat"→"text", Mistral: "base"→"text")
|
|
236
|
+
* 6. Fallback to 'text' — safe default for most providers
|
|
237
|
+
*
|
|
238
|
+
* Note: aliases that resolve to 'text' are checked AFTER the ID heuristic (Tier 4)
|
|
239
|
+
* so that e.g. Mistral's `mistral-embed` (type: "base") still gets classified as
|
|
240
|
+
* 'embedding' via its model ID rather than being swallowed by the "base"→"text" alias. */
|
|
227
241
|
declare function defaultModelNormalizer(raw: Record<string, unknown>): AnyModel;
|
|
228
242
|
|
|
229
243
|
/** A fetch-compatible function signature. Used for SSR, testing, or proxy scenarios. */
|
|
@@ -466,4 +480,4 @@ type ImageModelSelectorProps = Omit<ModelSelectorProps, 'type'>;
|
|
|
466
480
|
/** Props for VideoModelSelector — same as ModelSelectorProps but without `type`. */
|
|
467
481
|
type VideoModelSelectorProps = Omit<ModelSelectorProps, 'type'>;
|
|
468
482
|
|
|
469
|
-
export { type AnyModel, type AsrModel, type AsrPricing, type BaseModel, type Deprecation, type EmbeddingModel, type EmbeddingPricing, type FetchFn, type ImageConstraints, type ImageModel, ImageModelSelector, type ImageModelSelectorProps, type ImagePricing, type InpaintConstraints, type InpaintModel, type InpaintPricing, MODEL_ID_TYPE_PATTERNS, type ModelNormalizer, ModelSelector, type ModelSelectorProps, type ModelType, type ResponseExtractor, SYSTEM_DEFAULT_VALUE, type TextCapabilities, type TextConstraints, type TextModel, TextModelSelector, type TextModelSelectorProps, type TextPricing, type TtsModel, type TtsPricing, type UpscaleModel, type UpscalePricing, type UseModelsProps, type UseModelsResult, type VideoConstraints, type VideoModel, VideoModelSelector, type VideoModelSelectorProps, defaultModelNormalizer, defaultResponseExtractor, extractBaseFields, formatAspectRatios, formatAudioPrice, formatContextLength, formatDuration, formatFlatPrice, formatPrice, formatResolutions, inferTypeFromId, isDeprecated, normalizeAsrModel, normalizeEmbeddingModel, normalizeImageModel, normalizeInpaintModel, normalizeTextModel, normalizeTtsModel, normalizeUpscaleModel, normalizeVideoModel, toNum, useModels };
|
|
483
|
+
export { type AnyModel, type AsrModel, type AsrPricing, type BaseModel, type Deprecation, type EmbeddingModel, type EmbeddingPricing, type FetchFn, type ImageConstraints, type ImageModel, ImageModelSelector, type ImageModelSelectorProps, type ImagePricing, type InpaintConstraints, type InpaintModel, type InpaintPricing, MODEL_ID_TYPE_PATTERNS, type ModelNormalizer, ModelSelector, type ModelSelectorProps, type ModelType, type ResponseExtractor, SYSTEM_DEFAULT_VALUE, TYPE_ALIASES, type TextCapabilities, type TextConstraints, type TextModel, TextModelSelector, type TextModelSelectorProps, type TextPricing, type TtsModel, type TtsPricing, type UpscaleModel, type UpscalePricing, type UseModelsProps, type UseModelsResult, type VideoConstraints, type VideoModel, VideoModelSelector, type VideoModelSelectorProps, defaultModelNormalizer, defaultResponseExtractor, extractBaseFields, formatAspectRatios, formatAudioPrice, formatContextLength, formatDuration, formatFlatPrice, formatPrice, formatResolutions, inferTypeFromId, isDeprecated, normalizeAsrModel, normalizeEmbeddingModel, normalizeImageModel, normalizeInpaintModel, normalizeTextModel, normalizeTtsModel, normalizeUpscaleModel, normalizeVideoModel, toNum, useModels };
|
package/dist/index.js
CHANGED
|
@@ -10,11 +10,12 @@ import { useState, useEffect, useRef, useMemo } from "react";
|
|
|
10
10
|
|
|
11
11
|
// src/utils/normalizers/type-inference.ts
|
|
12
12
|
var MODEL_ID_TYPE_PATTERNS = [
|
|
13
|
-
[/\b(embed|embedding)\b/i, "embedding"],
|
|
14
|
-
[/\b(dall-e|stable-diffusion|sdxl|midjourney|flux)\b/i, "image"],
|
|
13
|
+
[/\b(embed|embedding|embedqa)\b/i, "embedding"],
|
|
14
|
+
[/\b(dall-e|stable-diffusion|sdxl|midjourney|flux|imagen)\b/i, "image"],
|
|
15
|
+
[/\b(gpt-image|image-preview)\b/i, "image"],
|
|
15
16
|
[/\b(tts)\b/i, "tts"],
|
|
16
17
|
[/\b(whisper|asr)\b/i, "asr"],
|
|
17
|
-
[/\b(sora|video|wan)\b/i, "video"],
|
|
18
|
+
[/\b(sora|video|wan|kling)\b/i, "video"],
|
|
18
19
|
[/\b(inpaint)\b/i, "inpaint"],
|
|
19
20
|
[/\b(upscale|esrgan)\b/i, "upscale"]
|
|
20
21
|
];
|
|
@@ -277,10 +278,45 @@ function defaultResponseExtractor(body) {
|
|
|
277
278
|
return [];
|
|
278
279
|
}
|
|
279
280
|
var VALID_TYPES = /* @__PURE__ */ new Set(["text", "image", "video", "inpaint", "embedding", "tts", "asr", "upscale"]);
|
|
281
|
+
var TYPE_ALIASES = {
|
|
282
|
+
// Together AI
|
|
283
|
+
chat: "text",
|
|
284
|
+
language: "text",
|
|
285
|
+
// Vercel AI Gateway, Together AI
|
|
286
|
+
base: "text",
|
|
287
|
+
// Mistral AI
|
|
288
|
+
moderation: "text",
|
|
289
|
+
// Together AI
|
|
290
|
+
rerank: "text",
|
|
291
|
+
// Together AI
|
|
292
|
+
audio: "tts",
|
|
293
|
+
// Together AI (Cartesia Sonic etc.)
|
|
294
|
+
transcribe: "asr"
|
|
295
|
+
// Together AI (Whisper etc.)
|
|
296
|
+
// image, video, embedding already match VALID_TYPES directly
|
|
297
|
+
};
|
|
298
|
+
function inferTypeFromArchitecture(raw) {
|
|
299
|
+
const arch = raw.architecture;
|
|
300
|
+
if (!arch) return void 0;
|
|
301
|
+
const outputMods = arch.output_modalities;
|
|
302
|
+
if (!Array.isArray(outputMods) || outputMods.length === 0) return void 0;
|
|
303
|
+
if (outputMods.includes("image") && !outputMods.includes("text")) return "image";
|
|
304
|
+
if (outputMods.length === 1 && outputMods[0] === "audio") return "tts";
|
|
305
|
+
return void 0;
|
|
306
|
+
}
|
|
280
307
|
function defaultModelNormalizer(raw) {
|
|
281
308
|
const id = raw.id ?? raw.model_id ?? "";
|
|
282
309
|
const rawType = raw.type;
|
|
283
|
-
const
|
|
310
|
+
const aliasedType = rawType && rawType in TYPE_ALIASES ? TYPE_ALIASES[rawType] : void 0;
|
|
311
|
+
const type = (
|
|
312
|
+
// 1. Direct match against canonical types (Venice)
|
|
313
|
+
(rawType && VALID_TYPES.has(rawType) ? rawType : void 0) ?? // 2. Non-text alias (e.g. "audio"→"tts", "transcribe"→"asr") — these are specific enough to trust
|
|
314
|
+
(aliasedType && aliasedType !== "text" ? aliasedType : void 0) ?? // 3. Architecture-based inference (OpenRouter output_modalities)
|
|
315
|
+
inferTypeFromArchitecture(raw) ?? // 4. Heuristic from model ID patterns
|
|
316
|
+
inferTypeFromId(id) ?? // 5. Text-aliased type (e.g. "chat"→"text", "base"→"text", "language"→"text")
|
|
317
|
+
aliasedType ?? // 6. Default to 'text'
|
|
318
|
+
"text"
|
|
319
|
+
);
|
|
284
320
|
switch (type) {
|
|
285
321
|
case "text":
|
|
286
322
|
return normalizeTextModel(raw);
|
|
@@ -325,10 +361,10 @@ function useModels(props) {
|
|
|
325
361
|
setError(null);
|
|
326
362
|
return;
|
|
327
363
|
}
|
|
328
|
-
if (!/^https
|
|
364
|
+
if (!/^(https?:\/\/|\/(?!\/))/i.test(baseUrl)) {
|
|
329
365
|
setAllModels([]);
|
|
330
366
|
setLoading(false);
|
|
331
|
-
setError(new Error(`Invalid baseUrl scheme: URL must start with http
|
|
367
|
+
setError(new Error(`Invalid baseUrl scheme: URL must start with http://, https://, or /`));
|
|
332
368
|
return;
|
|
333
369
|
}
|
|
334
370
|
const controller = new AbortController();
|
|
@@ -1346,6 +1382,7 @@ export {
|
|
|
1346
1382
|
MODEL_ID_TYPE_PATTERNS,
|
|
1347
1383
|
ModelSelector,
|
|
1348
1384
|
SYSTEM_DEFAULT_VALUE,
|
|
1385
|
+
TYPE_ALIASES,
|
|
1349
1386
|
TextModelSelector,
|
|
1350
1387
|
VideoModelSelector,
|
|
1351
1388
|
defaultModelNormalizer,
|
package/dist/utils.cjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/utils/normalizers/type-inference.ts
|
|
2
2
|
var MODEL_ID_TYPE_PATTERNS = [
|
|
3
|
-
[/\b(embed|embedding)\b/i, "embedding"],
|
|
4
|
-
[/\b(dall-e|stable-diffusion|sdxl|midjourney|flux)\b/i, "image"],
|
|
3
|
+
[/\b(embed|embedding|embedqa)\b/i, "embedding"],
|
|
4
|
+
[/\b(dall-e|stable-diffusion|sdxl|midjourney|flux|imagen)\b/i, "image"],
|
|
5
|
+
[/\b(gpt-image|image-preview)\b/i, "image"],
|
|
5
6
|
[/\b(tts)\b/i, "tts"],
|
|
6
7
|
[/\b(whisper|asr)\b/i, "asr"],
|
|
7
|
-
[/\b(sora|video|wan)\b/i, "video"],
|
|
8
|
+
[/\b(sora|video|wan|kling)\b/i, "video"],
|
|
8
9
|
[/\b(inpaint)\b/i, "inpaint"],
|
|
9
10
|
[/\b(upscale|esrgan)\b/i, "upscale"]
|
|
10
11
|
];
|
|
@@ -267,10 +268,45 @@ function defaultResponseExtractor(body) {
|
|
|
267
268
|
return [];
|
|
268
269
|
}
|
|
269
270
|
var VALID_TYPES = /* @__PURE__ */ new Set(["text", "image", "video", "inpaint", "embedding", "tts", "asr", "upscale"]);
|
|
271
|
+
var TYPE_ALIASES = {
|
|
272
|
+
// Together AI
|
|
273
|
+
chat: "text",
|
|
274
|
+
language: "text",
|
|
275
|
+
// Vercel AI Gateway, Together AI
|
|
276
|
+
base: "text",
|
|
277
|
+
// Mistral AI
|
|
278
|
+
moderation: "text",
|
|
279
|
+
// Together AI
|
|
280
|
+
rerank: "text",
|
|
281
|
+
// Together AI
|
|
282
|
+
audio: "tts",
|
|
283
|
+
// Together AI (Cartesia Sonic etc.)
|
|
284
|
+
transcribe: "asr"
|
|
285
|
+
// Together AI (Whisper etc.)
|
|
286
|
+
// image, video, embedding already match VALID_TYPES directly
|
|
287
|
+
};
|
|
288
|
+
function inferTypeFromArchitecture(raw) {
|
|
289
|
+
const arch = raw.architecture;
|
|
290
|
+
if (!arch) return void 0;
|
|
291
|
+
const outputMods = arch.output_modalities;
|
|
292
|
+
if (!Array.isArray(outputMods) || outputMods.length === 0) return void 0;
|
|
293
|
+
if (outputMods.includes("image") && !outputMods.includes("text")) return "image";
|
|
294
|
+
if (outputMods.length === 1 && outputMods[0] === "audio") return "tts";
|
|
295
|
+
return void 0;
|
|
296
|
+
}
|
|
270
297
|
function defaultModelNormalizer(raw) {
|
|
271
298
|
const id = _nullishCoalesce(_nullishCoalesce(raw.id, () => ( raw.model_id)), () => ( ""));
|
|
272
299
|
const rawType = raw.type;
|
|
273
|
-
const
|
|
300
|
+
const aliasedType = rawType && rawType in TYPE_ALIASES ? TYPE_ALIASES[rawType] : void 0;
|
|
301
|
+
const type = (
|
|
302
|
+
// 1. Direct match against canonical types (Venice)
|
|
303
|
+
_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce((rawType && VALID_TYPES.has(rawType) ? rawType : void 0), () => ( // 2. Non-text alias (e.g. "audio"→"tts", "transcribe"→"asr") — these are specific enough to trust
|
|
304
|
+
(aliasedType && aliasedType !== "text" ? aliasedType : void 0))), () => ( // 3. Architecture-based inference (OpenRouter output_modalities)
|
|
305
|
+
inferTypeFromArchitecture(raw))), () => ( // 4. Heuristic from model ID patterns
|
|
306
|
+
inferTypeFromId(id))), () => ( // 5. Text-aliased type (e.g. "chat"→"text", "base"→"text", "language"→"text")
|
|
307
|
+
aliasedType)), () => ( // 6. Default to 'text'
|
|
308
|
+
"text"))
|
|
309
|
+
);
|
|
274
310
|
switch (type) {
|
|
275
311
|
case "text":
|
|
276
312
|
return normalizeTextModel(raw);
|
|
@@ -368,4 +404,5 @@ function formatAspectRatios(ratios) {
|
|
|
368
404
|
|
|
369
405
|
|
|
370
406
|
|
|
371
|
-
|
|
407
|
+
|
|
408
|
+
exports.MODEL_ID_TYPE_PATTERNS = MODEL_ID_TYPE_PATTERNS; exports.TYPE_ALIASES = TYPE_ALIASES; exports.defaultModelNormalizer = defaultModelNormalizer; exports.defaultResponseExtractor = defaultResponseExtractor; exports.extractBaseFields = extractBaseFields; exports.formatAspectRatios = formatAspectRatios; exports.formatAudioPrice = formatAudioPrice; exports.formatContextLength = formatContextLength; exports.formatDuration = formatDuration; exports.formatFlatPrice = formatFlatPrice; exports.formatPrice = formatPrice; exports.formatResolutions = formatResolutions; exports.inferTypeFromId = inferTypeFromId; exports.isDeprecated = isDeprecated; exports.normalizeAsrModel = normalizeAsrModel; exports.normalizeEmbeddingModel = normalizeEmbeddingModel; exports.normalizeImageModel = normalizeImageModel; exports.normalizeInpaintModel = normalizeInpaintModel; exports.normalizeTextModel = normalizeTextModel; exports.normalizeTtsModel = normalizeTtsModel; exports.normalizeUpscaleModel = normalizeUpscaleModel; exports.normalizeVideoModel = normalizeVideoModel; exports.toNum = toNum;
|
package/dist/utils.d.cts
CHANGED
|
@@ -175,7 +175,10 @@ declare function extractBaseFields(raw: Record<string, unknown>): Omit<BaseModel
|
|
|
175
175
|
|
|
176
176
|
/** Known model ID patterns for heuristic type inference.
|
|
177
177
|
* Used when the API response lacks an explicit `type` field (non-Venice providers).
|
|
178
|
-
* Exported so consumers can inspect or extend.
|
|
178
|
+
* Exported so consumers can inspect or extend.
|
|
179
|
+
*
|
|
180
|
+
* Patterns are tested in order — first match wins. More specific patterns should
|
|
181
|
+
* come before broader ones (e.g. "gpt-image" before generic "image"). */
|
|
179
182
|
declare const MODEL_ID_TYPE_PATTERNS: Array<[RegExp, ModelType]>;
|
|
180
183
|
/** Infer model type from its ID using naming conventions.
|
|
181
184
|
* Returns undefined if no pattern matches (caller should fall back to 'text'). */
|
|
@@ -217,11 +220,22 @@ type ModelNormalizer = (raw: Record<string, unknown>) => AnyModel;
|
|
|
217
220
|
* - `{ models: [...] }` → return models
|
|
218
221
|
* - Fallback → empty array */
|
|
219
222
|
declare function defaultResponseExtractor(body: Record<string, unknown> | unknown[]): Record<string, unknown>[];
|
|
223
|
+
/** Maps provider-specific type strings to our canonical ModelType values.
|
|
224
|
+
* Covers vocabulary differences across Together AI, Vercel AI Gateway, Mistral, etc.
|
|
225
|
+
* Exported so consumers can inspect or extend. */
|
|
226
|
+
declare const TYPE_ALIASES: Record<string, ModelType>;
|
|
220
227
|
/** Default dispatching model normalizer.
|
|
221
|
-
* Uses
|
|
222
|
-
* 1. Explicit `raw.type` field (Venice, custom providers)
|
|
223
|
-
* 2.
|
|
224
|
-
* 3.
|
|
228
|
+
* Uses multi-tier type resolution:
|
|
229
|
+
* 1. Explicit `raw.type` field matching our canonical types (Venice, custom providers)
|
|
230
|
+
* 2. Non-text alias mapping for provider-specific vocabulary (Together AI: "audio"→"tts")
|
|
231
|
+
* 3. Architecture-based inference from `output_modalities` (OpenRouter)
|
|
232
|
+
* 4. Heuristic inference from model ID patterns (OpenAI, Nvidia, etc.)
|
|
233
|
+
* 5. Text-aliased type (Together AI: "chat"→"text", Mistral: "base"→"text")
|
|
234
|
+
* 6. Fallback to 'text' — safe default for most providers
|
|
235
|
+
*
|
|
236
|
+
* Note: aliases that resolve to 'text' are checked AFTER the ID heuristic (Tier 4)
|
|
237
|
+
* so that e.g. Mistral's `mistral-embed` (type: "base") still gets classified as
|
|
238
|
+
* 'embedding' via its model ID rather than being swallowed by the "base"→"text" alias. */
|
|
225
239
|
declare function defaultModelNormalizer(raw: Record<string, unknown>): AnyModel;
|
|
226
240
|
|
|
227
241
|
/** Check whether a deprecation date is in the past.
|
|
@@ -301,4 +315,4 @@ declare function formatResolutions(resolutions: string[] | undefined | null): st
|
|
|
301
315
|
*/
|
|
302
316
|
declare function formatAspectRatios(ratios: string[] | undefined | null): string;
|
|
303
317
|
|
|
304
|
-
export { type AnyModel, type AsrModel, type AsrPricing, type BaseModel, type Deprecation, type EmbeddingModel, type EmbeddingPricing, type ImageConstraints, type ImageModel, type ImagePricing, type InpaintConstraints, type InpaintModel, type InpaintPricing, MODEL_ID_TYPE_PATTERNS, type ModelNormalizer, type ModelType, type ResponseExtractor, type TextCapabilities, type TextConstraints, type TextModel, type TextPricing, type TtsModel, type TtsPricing, type UpscaleModel, type UpscalePricing, type VideoConstraints, type VideoModel, defaultModelNormalizer, defaultResponseExtractor, extractBaseFields, formatAspectRatios, formatAudioPrice, formatContextLength, formatDuration, formatFlatPrice, formatPrice, formatResolutions, inferTypeFromId, isDeprecated, normalizeAsrModel, normalizeEmbeddingModel, normalizeImageModel, normalizeInpaintModel, normalizeTextModel, normalizeTtsModel, normalizeUpscaleModel, normalizeVideoModel, toNum };
|
|
318
|
+
export { type AnyModel, type AsrModel, type AsrPricing, type BaseModel, type Deprecation, type EmbeddingModel, type EmbeddingPricing, type ImageConstraints, type ImageModel, type ImagePricing, type InpaintConstraints, type InpaintModel, type InpaintPricing, MODEL_ID_TYPE_PATTERNS, type ModelNormalizer, type ModelType, type ResponseExtractor, TYPE_ALIASES, type TextCapabilities, type TextConstraints, type TextModel, type TextPricing, type TtsModel, type TtsPricing, type UpscaleModel, type UpscalePricing, type VideoConstraints, type VideoModel, defaultModelNormalizer, defaultResponseExtractor, extractBaseFields, formatAspectRatios, formatAudioPrice, formatContextLength, formatDuration, formatFlatPrice, formatPrice, formatResolutions, inferTypeFromId, isDeprecated, normalizeAsrModel, normalizeEmbeddingModel, normalizeImageModel, normalizeInpaintModel, normalizeTextModel, normalizeTtsModel, normalizeUpscaleModel, normalizeVideoModel, toNum };
|
package/dist/utils.d.ts
CHANGED
|
@@ -175,7 +175,10 @@ declare function extractBaseFields(raw: Record<string, unknown>): Omit<BaseModel
|
|
|
175
175
|
|
|
176
176
|
/** Known model ID patterns for heuristic type inference.
|
|
177
177
|
* Used when the API response lacks an explicit `type` field (non-Venice providers).
|
|
178
|
-
* Exported so consumers can inspect or extend.
|
|
178
|
+
* Exported so consumers can inspect or extend.
|
|
179
|
+
*
|
|
180
|
+
* Patterns are tested in order — first match wins. More specific patterns should
|
|
181
|
+
* come before broader ones (e.g. "gpt-image" before generic "image"). */
|
|
179
182
|
declare const MODEL_ID_TYPE_PATTERNS: Array<[RegExp, ModelType]>;
|
|
180
183
|
/** Infer model type from its ID using naming conventions.
|
|
181
184
|
* Returns undefined if no pattern matches (caller should fall back to 'text'). */
|
|
@@ -217,11 +220,22 @@ type ModelNormalizer = (raw: Record<string, unknown>) => AnyModel;
|
|
|
217
220
|
* - `{ models: [...] }` → return models
|
|
218
221
|
* - Fallback → empty array */
|
|
219
222
|
declare function defaultResponseExtractor(body: Record<string, unknown> | unknown[]): Record<string, unknown>[];
|
|
223
|
+
/** Maps provider-specific type strings to our canonical ModelType values.
|
|
224
|
+
* Covers vocabulary differences across Together AI, Vercel AI Gateway, Mistral, etc.
|
|
225
|
+
* Exported so consumers can inspect or extend. */
|
|
226
|
+
declare const TYPE_ALIASES: Record<string, ModelType>;
|
|
220
227
|
/** Default dispatching model normalizer.
|
|
221
|
-
* Uses
|
|
222
|
-
* 1. Explicit `raw.type` field (Venice, custom providers)
|
|
223
|
-
* 2.
|
|
224
|
-
* 3.
|
|
228
|
+
* Uses multi-tier type resolution:
|
|
229
|
+
* 1. Explicit `raw.type` field matching our canonical types (Venice, custom providers)
|
|
230
|
+
* 2. Non-text alias mapping for provider-specific vocabulary (Together AI: "audio"→"tts")
|
|
231
|
+
* 3. Architecture-based inference from `output_modalities` (OpenRouter)
|
|
232
|
+
* 4. Heuristic inference from model ID patterns (OpenAI, Nvidia, etc.)
|
|
233
|
+
* 5. Text-aliased type (Together AI: "chat"→"text", Mistral: "base"→"text")
|
|
234
|
+
* 6. Fallback to 'text' — safe default for most providers
|
|
235
|
+
*
|
|
236
|
+
* Note: aliases that resolve to 'text' are checked AFTER the ID heuristic (Tier 4)
|
|
237
|
+
* so that e.g. Mistral's `mistral-embed` (type: "base") still gets classified as
|
|
238
|
+
* 'embedding' via its model ID rather than being swallowed by the "base"→"text" alias. */
|
|
225
239
|
declare function defaultModelNormalizer(raw: Record<string, unknown>): AnyModel;
|
|
226
240
|
|
|
227
241
|
/** Check whether a deprecation date is in the past.
|
|
@@ -301,4 +315,4 @@ declare function formatResolutions(resolutions: string[] | undefined | null): st
|
|
|
301
315
|
*/
|
|
302
316
|
declare function formatAspectRatios(ratios: string[] | undefined | null): string;
|
|
303
317
|
|
|
304
|
-
export { type AnyModel, type AsrModel, type AsrPricing, type BaseModel, type Deprecation, type EmbeddingModel, type EmbeddingPricing, type ImageConstraints, type ImageModel, type ImagePricing, type InpaintConstraints, type InpaintModel, type InpaintPricing, MODEL_ID_TYPE_PATTERNS, type ModelNormalizer, type ModelType, type ResponseExtractor, type TextCapabilities, type TextConstraints, type TextModel, type TextPricing, type TtsModel, type TtsPricing, type UpscaleModel, type UpscalePricing, type VideoConstraints, type VideoModel, defaultModelNormalizer, defaultResponseExtractor, extractBaseFields, formatAspectRatios, formatAudioPrice, formatContextLength, formatDuration, formatFlatPrice, formatPrice, formatResolutions, inferTypeFromId, isDeprecated, normalizeAsrModel, normalizeEmbeddingModel, normalizeImageModel, normalizeInpaintModel, normalizeTextModel, normalizeTtsModel, normalizeUpscaleModel, normalizeVideoModel, toNum };
|
|
318
|
+
export { type AnyModel, type AsrModel, type AsrPricing, type BaseModel, type Deprecation, type EmbeddingModel, type EmbeddingPricing, type ImageConstraints, type ImageModel, type ImagePricing, type InpaintConstraints, type InpaintModel, type InpaintPricing, MODEL_ID_TYPE_PATTERNS, type ModelNormalizer, type ModelType, type ResponseExtractor, TYPE_ALIASES, type TextCapabilities, type TextConstraints, type TextModel, type TextPricing, type TtsModel, type TtsPricing, type UpscaleModel, type UpscalePricing, type VideoConstraints, type VideoModel, defaultModelNormalizer, defaultResponseExtractor, extractBaseFields, formatAspectRatios, formatAudioPrice, formatContextLength, formatDuration, formatFlatPrice, formatPrice, formatResolutions, inferTypeFromId, isDeprecated, normalizeAsrModel, normalizeEmbeddingModel, normalizeImageModel, normalizeInpaintModel, normalizeTextModel, normalizeTtsModel, normalizeUpscaleModel, normalizeVideoModel, toNum };
|
package/dist/utils.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// src/utils/normalizers/type-inference.ts
|
|
2
2
|
var MODEL_ID_TYPE_PATTERNS = [
|
|
3
|
-
[/\b(embed|embedding)\b/i, "embedding"],
|
|
4
|
-
[/\b(dall-e|stable-diffusion|sdxl|midjourney|flux)\b/i, "image"],
|
|
3
|
+
[/\b(embed|embedding|embedqa)\b/i, "embedding"],
|
|
4
|
+
[/\b(dall-e|stable-diffusion|sdxl|midjourney|flux|imagen)\b/i, "image"],
|
|
5
|
+
[/\b(gpt-image|image-preview)\b/i, "image"],
|
|
5
6
|
[/\b(tts)\b/i, "tts"],
|
|
6
7
|
[/\b(whisper|asr)\b/i, "asr"],
|
|
7
|
-
[/\b(sora|video|wan)\b/i, "video"],
|
|
8
|
+
[/\b(sora|video|wan|kling)\b/i, "video"],
|
|
8
9
|
[/\b(inpaint)\b/i, "inpaint"],
|
|
9
10
|
[/\b(upscale|esrgan)\b/i, "upscale"]
|
|
10
11
|
];
|
|
@@ -267,10 +268,45 @@ function defaultResponseExtractor(body) {
|
|
|
267
268
|
return [];
|
|
268
269
|
}
|
|
269
270
|
var VALID_TYPES = /* @__PURE__ */ new Set(["text", "image", "video", "inpaint", "embedding", "tts", "asr", "upscale"]);
|
|
271
|
+
var TYPE_ALIASES = {
|
|
272
|
+
// Together AI
|
|
273
|
+
chat: "text",
|
|
274
|
+
language: "text",
|
|
275
|
+
// Vercel AI Gateway, Together AI
|
|
276
|
+
base: "text",
|
|
277
|
+
// Mistral AI
|
|
278
|
+
moderation: "text",
|
|
279
|
+
// Together AI
|
|
280
|
+
rerank: "text",
|
|
281
|
+
// Together AI
|
|
282
|
+
audio: "tts",
|
|
283
|
+
// Together AI (Cartesia Sonic etc.)
|
|
284
|
+
transcribe: "asr"
|
|
285
|
+
// Together AI (Whisper etc.)
|
|
286
|
+
// image, video, embedding already match VALID_TYPES directly
|
|
287
|
+
};
|
|
288
|
+
function inferTypeFromArchitecture(raw) {
|
|
289
|
+
const arch = raw.architecture;
|
|
290
|
+
if (!arch) return void 0;
|
|
291
|
+
const outputMods = arch.output_modalities;
|
|
292
|
+
if (!Array.isArray(outputMods) || outputMods.length === 0) return void 0;
|
|
293
|
+
if (outputMods.includes("image") && !outputMods.includes("text")) return "image";
|
|
294
|
+
if (outputMods.length === 1 && outputMods[0] === "audio") return "tts";
|
|
295
|
+
return void 0;
|
|
296
|
+
}
|
|
270
297
|
function defaultModelNormalizer(raw) {
|
|
271
298
|
const id = raw.id ?? raw.model_id ?? "";
|
|
272
299
|
const rawType = raw.type;
|
|
273
|
-
const
|
|
300
|
+
const aliasedType = rawType && rawType in TYPE_ALIASES ? TYPE_ALIASES[rawType] : void 0;
|
|
301
|
+
const type = (
|
|
302
|
+
// 1. Direct match against canonical types (Venice)
|
|
303
|
+
(rawType && VALID_TYPES.has(rawType) ? rawType : void 0) ?? // 2. Non-text alias (e.g. "audio"→"tts", "transcribe"→"asr") — these are specific enough to trust
|
|
304
|
+
(aliasedType && aliasedType !== "text" ? aliasedType : void 0) ?? // 3. Architecture-based inference (OpenRouter output_modalities)
|
|
305
|
+
inferTypeFromArchitecture(raw) ?? // 4. Heuristic from model ID patterns
|
|
306
|
+
inferTypeFromId(id) ?? // 5. Text-aliased type (e.g. "chat"→"text", "base"→"text", "language"→"text")
|
|
307
|
+
aliasedType ?? // 6. Default to 'text'
|
|
308
|
+
"text"
|
|
309
|
+
);
|
|
274
310
|
switch (type) {
|
|
275
311
|
case "text":
|
|
276
312
|
return normalizeTextModel(raw);
|
|
@@ -347,6 +383,7 @@ function formatAspectRatios(ratios) {
|
|
|
347
383
|
}
|
|
348
384
|
export {
|
|
349
385
|
MODEL_ID_TYPE_PATTERNS,
|
|
386
|
+
TYPE_ALIASES,
|
|
350
387
|
defaultModelNormalizer,
|
|
351
388
|
defaultResponseExtractor,
|
|
352
389
|
extractBaseFields,
|