@cappasoft/openrouter-models 1.0.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/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # @cappasoft/openrouter-models
2
+
3
+ Headless client for fetching and grouping OpenRouter models.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @cappasoft/openrouter-models
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { createOpenRouterModelsClient } from '@cappasoft/openrouter-models'
15
+
16
+ const client = createOpenRouterModelsClient({
17
+ apiKey: 'sk-or-v1-...',
18
+ })
19
+
20
+ const { models, categories } = await client.listModels()
21
+ console.log(models.length, categories.map((c) => c.name))
22
+ ```
23
+
24
+
@@ -0,0 +1,74 @@
1
+ interface OpenRouterModel {
2
+ id: string;
3
+ name: string;
4
+ description?: string;
5
+ context_length: number;
6
+ pricing: {
7
+ prompt: string;
8
+ completion: string;
9
+ };
10
+ top_provider?: {
11
+ max_completion_tokens?: number;
12
+ };
13
+ architecture?: {
14
+ modality: string;
15
+ tokenizer: string;
16
+ instruct_type?: string;
17
+ };
18
+ }
19
+ interface ModelCategory {
20
+ name: string;
21
+ models: OpenRouterModel[];
22
+ }
23
+
24
+ declare const DEFAULT_ENDPOINT = "https://openrouter.ai/api/v1/models";
25
+ declare const CATEGORY_MAP: Record<string, string>;
26
+ declare const RECOMMENDED_MODELS: readonly ["google/gemini-3-pro-preview", "openai/gpt-4o", "openai/gpt-4o-mini", "anthropic/claude-sonnet-4-20250514", "anthropic/claude-3.5-sonnet", "x-ai/grok-2-1212", "x-ai/grok-3-mini-beta", "google/gemini-2.0-flash-001", "deepseek/deepseek-chat", "deepseek/deepseek-r1", "meta-llama/llama-3.3-70b-instruct", "mistralai/mistral-large-2411"];
27
+ declare const DEFAULT_CACHE_KEY = "openrouter_models_cache_v1";
28
+ declare const DEFAULT_CACHE_TTL_MS: number;
29
+ declare const DEFAULT_EXCLUDED_PATTERNS: RegExp[];
30
+ declare const DEFAULT_CATEGORY_ORDER: readonly ["OpenAI", "Anthropic", "Google", "Meta", "Mistral", "DeepSeek", "Qwen", "xAI", "Cohere", "Perplexity", "NVIDIA", "Other"];
31
+
32
+ declare function getCategory(modelId: string): string;
33
+ declare function isModelExcluded(model: OpenRouterModel, excludedPatterns?: RegExp[]): boolean;
34
+ declare function buildCategories(models: OpenRouterModel[], options?: {
35
+ includeAllCategories?: boolean;
36
+ categoryOrder?: readonly string[];
37
+ }): ModelCategory[];
38
+ declare function filterAndGroup(allModels: OpenRouterModel[], options?: {
39
+ filterToRecommended?: boolean;
40
+ includeAllCategories?: boolean;
41
+ recommendedModels?: readonly string[];
42
+ excludedPatterns?: RegExp[];
43
+ }): {
44
+ filteredModels: OpenRouterModel[];
45
+ categoriesArray: ModelCategory[];
46
+ };
47
+
48
+ interface OpenRouterModelsClient {
49
+ listModels(): Promise<{
50
+ models: OpenRouterModel[];
51
+ categories: ModelCategory[];
52
+ lastUpdated: number | null;
53
+ }>;
54
+ refresh(): Promise<{
55
+ models: OpenRouterModel[];
56
+ categories: ModelCategory[];
57
+ lastUpdated: number | null;
58
+ }>;
59
+ }
60
+ interface CacheProvider {
61
+ get(key: string): string | null;
62
+ set(key: string, value: string): void;
63
+ ttlMs?: number;
64
+ cacheKey?: string;
65
+ }
66
+ interface CreateOpenRouterModelsClientOptions {
67
+ apiKey: string;
68
+ endpoint?: string;
69
+ fetcher?: typeof fetch;
70
+ cache?: CacheProvider;
71
+ }
72
+ declare function createOpenRouterModelsClient(options: CreateOpenRouterModelsClientOptions): OpenRouterModelsClient;
73
+
74
+ export { CATEGORY_MAP, type CacheProvider, type CreateOpenRouterModelsClientOptions, DEFAULT_CACHE_KEY, DEFAULT_CACHE_TTL_MS, DEFAULT_CATEGORY_ORDER, DEFAULT_ENDPOINT, DEFAULT_EXCLUDED_PATTERNS, type ModelCategory, type OpenRouterModel, type OpenRouterModelsClient, RECOMMENDED_MODELS, buildCategories, createOpenRouterModelsClient, filterAndGroup, getCategory, isModelExcluded };
package/dist/index.js ADDED
@@ -0,0 +1,192 @@
1
+ // src/constants.ts
2
+ var DEFAULT_ENDPOINT = "https://openrouter.ai/api/v1/models";
3
+ var CATEGORY_MAP = {
4
+ openai: "OpenAI",
5
+ anthropic: "Anthropic",
6
+ google: "Google",
7
+ "meta-llama": "Meta",
8
+ mistralai: "Mistral",
9
+ deepseek: "DeepSeek",
10
+ cohere: "Cohere",
11
+ qwen: "Qwen",
12
+ "x-ai": "xAI",
13
+ perplexity: "Perplexity",
14
+ nvidia: "NVIDIA"
15
+ };
16
+ var RECOMMENDED_MODELS = [
17
+ "google/gemini-3-pro-preview",
18
+ "openai/gpt-4o",
19
+ "openai/gpt-4o-mini",
20
+ "anthropic/claude-sonnet-4-20250514",
21
+ "anthropic/claude-3.5-sonnet",
22
+ "x-ai/grok-2-1212",
23
+ "x-ai/grok-3-mini-beta",
24
+ "google/gemini-2.0-flash-001",
25
+ "deepseek/deepseek-chat",
26
+ "deepseek/deepseek-r1",
27
+ "meta-llama/llama-3.3-70b-instruct",
28
+ "mistralai/mistral-large-2411"
29
+ ];
30
+ var DEFAULT_CACHE_KEY = "openrouter_models_cache_v1";
31
+ var DEFAULT_CACHE_TTL_MS = 60 * 60 * 1e3;
32
+ var DEFAULT_EXCLUDED_PATTERNS = [
33
+ /vision/i,
34
+ /image/i,
35
+ /audio/i,
36
+ // :free models are now allowed as some are high quality previews
37
+ /:beta$/,
38
+ /extended$/
39
+ ];
40
+ var DEFAULT_CATEGORY_ORDER = [
41
+ "OpenAI",
42
+ "Anthropic",
43
+ "Google",
44
+ "Meta",
45
+ "Mistral",
46
+ "DeepSeek",
47
+ "Qwen",
48
+ "xAI",
49
+ "Cohere",
50
+ "Perplexity",
51
+ "NVIDIA",
52
+ "Other"
53
+ ];
54
+
55
+ // src/grouping.ts
56
+ function getCategory(modelId) {
57
+ const prefix = modelId.split("/")[0];
58
+ return CATEGORY_MAP[prefix] || "Other";
59
+ }
60
+ function isModelExcluded(model, excludedPatterns = DEFAULT_EXCLUDED_PATTERNS) {
61
+ return excludedPatterns.some((pattern) => pattern.test(model.id));
62
+ }
63
+ function buildCategories(models, options) {
64
+ const categoryMap = /* @__PURE__ */ new Map();
65
+ const categoryOrder = options?.categoryOrder ?? DEFAULT_CATEGORY_ORDER;
66
+ for (const model of models) {
67
+ const category = getCategory(model.id);
68
+ if (!options?.includeAllCategories && !Object.values(CATEGORY_MAP).includes(category)) {
69
+ continue;
70
+ }
71
+ if (!categoryMap.has(category)) categoryMap.set(category, []);
72
+ categoryMap.get(category).push(model);
73
+ }
74
+ const categoriesArray = [];
75
+ for (const catName of categoryOrder) {
76
+ const catModels = categoryMap.get(catName);
77
+ if (catModels && catModels.length > 0) {
78
+ categoriesArray.push({ name: catName, models: catModels });
79
+ }
80
+ }
81
+ for (const [catName, catModels] of categoryMap) {
82
+ if (!categoryOrder.includes(catName) && catModels.length > 0) {
83
+ categoriesArray.push({ name: catName, models: catModels });
84
+ }
85
+ }
86
+ return categoriesArray;
87
+ }
88
+ function filterAndGroup(allModels, options) {
89
+ const excludedPatterns = options?.excludedPatterns ?? DEFAULT_EXCLUDED_PATTERNS;
90
+ let filteredModels = allModels.filter((m) => !isModelExcluded(m, excludedPatterns));
91
+ const recommendedList = options?.recommendedModels ?? RECOMMENDED_MODELS;
92
+ if (options?.filterToRecommended) {
93
+ const recommendedSet = new Set(recommendedList);
94
+ filteredModels = filteredModels.filter((m) => recommendedSet.has(m.id));
95
+ filteredModels.sort((a, b) => recommendedList.indexOf(a.id) - recommendedList.indexOf(b.id));
96
+ } else {
97
+ filteredModels.sort((a, b) => a.name.localeCompare(b.name));
98
+ }
99
+ const categoriesArray = buildCategories(filteredModels, { includeAllCategories: options?.includeAllCategories });
100
+ return { filteredModels, categoriesArray };
101
+ }
102
+
103
+ // src/client.ts
104
+ function defaultCacheProvider() {
105
+ try {
106
+ if (typeof window === "undefined") return null;
107
+ if (!("localStorage" in window)) return null;
108
+ return {
109
+ get: (key) => window.localStorage.getItem(key),
110
+ set: (key, value) => window.localStorage.setItem(key, value),
111
+ ttlMs: DEFAULT_CACHE_TTL_MS,
112
+ cacheKey: DEFAULT_CACHE_KEY
113
+ };
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
118
+ function getRecommendedHeaders() {
119
+ const headers = {};
120
+ try {
121
+ if (typeof window !== "undefined") {
122
+ headers["HTTP-Referer"] = window.location.origin;
123
+ headers["X-Title"] = document.title || "App";
124
+ }
125
+ } catch {
126
+ }
127
+ return headers;
128
+ }
129
+ function createOpenRouterModelsClient(options) {
130
+ const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
131
+ const fetcher = options.fetcher ?? fetch;
132
+ const cache = options.cache ?? defaultCacheProvider();
133
+ const cacheKey = cache?.cacheKey ?? DEFAULT_CACHE_KEY;
134
+ const ttlMs = cache?.ttlMs ?? DEFAULT_CACHE_TTL_MS;
135
+ const readCache = () => {
136
+ if (!cache) return null;
137
+ try {
138
+ const raw = cache.get(cacheKey);
139
+ if (!raw) return null;
140
+ const parsed = JSON.parse(raw);
141
+ if (!parsed?.ts || !Array.isArray(parsed.data)) return null;
142
+ if (Date.now() - parsed.ts > ttlMs) return null;
143
+ return parsed;
144
+ } catch {
145
+ return null;
146
+ }
147
+ };
148
+ const writeCache = (models) => {
149
+ if (!cache) return;
150
+ try {
151
+ cache.set(cacheKey, JSON.stringify({ data: models, ts: Date.now() }));
152
+ } catch {
153
+ }
154
+ };
155
+ const fetchModels = async (force) => {
156
+ if (!force) {
157
+ const cached = readCache();
158
+ if (cached) return { models: cached.data, lastUpdated: cached.ts };
159
+ }
160
+ const headers = {
161
+ Authorization: `Bearer ${options.apiKey}`,
162
+ ...getRecommendedHeaders()
163
+ };
164
+ const response = await fetcher(endpoint, { headers });
165
+ if (!response.ok) {
166
+ const text = await response.text().catch(() => "");
167
+ throw new Error(`Failed to fetch models: ${response.status}${text ? ` - ${text}` : ""}`);
168
+ }
169
+ const json = await response.json();
170
+ const models = json.data ?? [];
171
+ writeCache(models);
172
+ return { models, lastUpdated: Date.now() };
173
+ };
174
+ const toResult = (models, lastUpdated) => {
175
+ const { categoriesArray } = filterAndGroup(models, { filterToRecommended: false, includeAllCategories: true });
176
+ return { models, categories: categoriesArray, lastUpdated };
177
+ };
178
+ return {
179
+ async listModels() {
180
+ const { models, lastUpdated } = await fetchModels(false);
181
+ return toResult(models, lastUpdated);
182
+ },
183
+ async refresh() {
184
+ const { models, lastUpdated } = await fetchModels(true);
185
+ return toResult(models, lastUpdated);
186
+ }
187
+ };
188
+ }
189
+
190
+ export { CATEGORY_MAP, DEFAULT_CACHE_KEY, DEFAULT_CACHE_TTL_MS, DEFAULT_CATEGORY_ORDER, DEFAULT_ENDPOINT, DEFAULT_EXCLUDED_PATTERNS, RECOMMENDED_MODELS, buildCategories, createOpenRouterModelsClient, filterAndGroup, getCategory, isModelExcluded };
191
+ //# sourceMappingURL=index.js.map
192
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/grouping.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export const DEFAULT_ENDPOINT = \"https://openrouter.ai/api/v1/models\"\n\n// Mapping from model ID prefix to category\nexport const CATEGORY_MAP: Record<string, string> = {\n openai: \"OpenAI\",\n anthropic: \"Anthropic\",\n google: \"Google\",\n \"meta-llama\": \"Meta\",\n mistralai: \"Mistral\",\n deepseek: \"DeepSeek\",\n cohere: \"Cohere\",\n qwen: \"Qwen\",\n \"x-ai\": \"xAI\",\n perplexity: \"Perplexity\",\n nvidia: \"NVIDIA\",\n}\n\n// Recommended models to show first (in order of preference)\nexport const RECOMMENDED_MODELS = [\n \"google/gemini-3-pro-preview\",\n \"openai/gpt-4o\",\n \"openai/gpt-4o-mini\",\n \"anthropic/claude-sonnet-4-20250514\",\n \"anthropic/claude-3.5-sonnet\",\n \"x-ai/grok-2-1212\",\n \"x-ai/grok-3-mini-beta\",\n \"google/gemini-2.0-flash-001\",\n \"deepseek/deepseek-chat\",\n \"deepseek/deepseek-r1\",\n \"meta-llama/llama-3.3-70b-instruct\",\n \"mistralai/mistral-large-2411\",\n] as const\n\nexport const DEFAULT_CACHE_KEY = \"openrouter_models_cache_v1\"\nexport const DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000 // 1h\n\n// Models to exclude (deprecated, vision-only, etc.)\nexport const DEFAULT_EXCLUDED_PATTERNS: RegExp[] = [\n /vision/i,\n /image/i,\n /audio/i,\n // :free models are now allowed as some are high quality previews\n /:beta$/,\n /extended$/,\n]\n\nexport const DEFAULT_CATEGORY_ORDER = [\n \"OpenAI\",\n \"Anthropic\",\n \"Google\",\n \"Meta\",\n \"Mistral\",\n \"DeepSeek\",\n \"Qwen\",\n \"xAI\",\n \"Cohere\",\n \"Perplexity\",\n \"NVIDIA\",\n \"Other\",\n] as const\n\n\n","import type { ModelCategory, OpenRouterModel } from \"./types\"\nimport { CATEGORY_MAP, DEFAULT_CATEGORY_ORDER, DEFAULT_EXCLUDED_PATTERNS, RECOMMENDED_MODELS } from \"./constants\"\n\nexport function getCategory(modelId: string): string {\n const prefix = modelId.split(\"/\")[0]\n return CATEGORY_MAP[prefix] || \"Other\"\n}\n\nexport function isModelExcluded(model: OpenRouterModel, excludedPatterns: RegExp[] = DEFAULT_EXCLUDED_PATTERNS): boolean {\n return excludedPatterns.some((pattern) => pattern.test(model.id))\n}\n\nexport function buildCategories(\n models: OpenRouterModel[],\n options?: { includeAllCategories?: boolean; categoryOrder?: readonly string[] }\n): ModelCategory[] {\n const categoryMap = new Map<string, OpenRouterModel[]>()\n const categoryOrder = options?.categoryOrder ?? DEFAULT_CATEGORY_ORDER\n\n for (const model of models) {\n const category = getCategory(model.id)\n\n if (!options?.includeAllCategories && !Object.values(CATEGORY_MAP).includes(category)) {\n continue\n }\n\n if (!categoryMap.has(category)) categoryMap.set(category, [])\n categoryMap.get(category)!.push(model)\n }\n\n // Convert to array and sort by predefined order\n const categoriesArray: ModelCategory[] = []\n for (const catName of categoryOrder) {\n const catModels = categoryMap.get(catName)\n if (catModels && catModels.length > 0) {\n categoriesArray.push({ name: catName, models: catModels })\n }\n }\n\n // Add any remaining categories not in the predefined order\n for (const [catName, catModels] of categoryMap) {\n if (!categoryOrder.includes(catName) && catModels.length > 0) {\n categoriesArray.push({ name: catName, models: catModels })\n }\n }\n\n return categoriesArray\n}\n\nexport function filterAndGroup(\n allModels: OpenRouterModel[],\n options?: {\n filterToRecommended?: boolean\n includeAllCategories?: boolean\n recommendedModels?: readonly string[]\n excludedPatterns?: RegExp[]\n }\n) {\n const excludedPatterns = options?.excludedPatterns ?? DEFAULT_EXCLUDED_PATTERNS\n\n let filteredModels = allModels.filter((m) => !isModelExcluded(m, excludedPatterns))\n\n const recommendedList = options?.recommendedModels ?? RECOMMENDED_MODELS\n if (options?.filterToRecommended) {\n const recommendedSet = new Set(recommendedList)\n filteredModels = filteredModels.filter((m) => recommendedSet.has(m.id))\n filteredModels.sort((a, b) => recommendedList.indexOf(a.id) - recommendedList.indexOf(b.id))\n } else {\n filteredModels.sort((a, b) => a.name.localeCompare(b.name))\n }\n\n const categoriesArray = buildCategories(filteredModels, { includeAllCategories: options?.includeAllCategories })\n return { filteredModels, categoriesArray }\n}\n\n\n","import type { ModelCategory, OpenRouterModel } from \"./types\"\nimport { DEFAULT_CACHE_KEY, DEFAULT_CACHE_TTL_MS, DEFAULT_ENDPOINT } from \"./constants\"\nimport { filterAndGroup } from \"./grouping\"\n\nexport interface OpenRouterModelsClient {\n listModels(): Promise<{\n models: OpenRouterModel[]\n categories: ModelCategory[]\n lastUpdated: number | null\n }>\n refresh(): Promise<{\n models: OpenRouterModel[]\n categories: ModelCategory[]\n lastUpdated: number | null\n }>\n}\n\nexport interface CacheProvider {\n get(key: string): string | null\n set(key: string, value: string): void\n ttlMs?: number\n cacheKey?: string\n}\n\nexport interface CreateOpenRouterModelsClientOptions {\n apiKey: string\n endpoint?: string\n fetcher?: typeof fetch\n cache?: CacheProvider\n}\n\nfunction defaultCacheProvider(): CacheProvider | null {\n try {\n if (typeof window === \"undefined\") return null\n if (!(\"localStorage\" in window)) return null\n return {\n get: (key) => window.localStorage.getItem(key),\n set: (key, value) => window.localStorage.setItem(key, value),\n ttlMs: DEFAULT_CACHE_TTL_MS,\n cacheKey: DEFAULT_CACHE_KEY,\n }\n } catch {\n return null\n }\n}\n\nfunction getRecommendedHeaders(): Record<string, string> {\n const headers: Record<string, string> = {}\n try {\n if (typeof window !== \"undefined\") {\n headers[\"HTTP-Referer\"] = window.location.origin\n headers[\"X-Title\"] = document.title || \"App\"\n }\n } catch {\n // ignore\n }\n return headers\n}\n\nexport function createOpenRouterModelsClient(options: CreateOpenRouterModelsClientOptions): OpenRouterModelsClient {\n const endpoint = options.endpoint ?? DEFAULT_ENDPOINT\n const fetcher = options.fetcher ?? fetch\n const cache = options.cache ?? defaultCacheProvider()\n const cacheKey = cache?.cacheKey ?? DEFAULT_CACHE_KEY\n const ttlMs = cache?.ttlMs ?? DEFAULT_CACHE_TTL_MS\n\n const readCache = (): { data: OpenRouterModel[]; ts: number } | null => {\n if (!cache) return null\n try {\n const raw = cache.get(cacheKey)\n if (!raw) return null\n const parsed = JSON.parse(raw) as { data: OpenRouterModel[]; ts: number }\n if (!parsed?.ts || !Array.isArray(parsed.data)) return null\n if (Date.now() - parsed.ts > ttlMs) return null\n return parsed\n } catch {\n return null\n }\n }\n\n const writeCache = (models: OpenRouterModel[]) => {\n if (!cache) return\n try {\n cache.set(cacheKey, JSON.stringify({ data: models, ts: Date.now() }))\n } catch {\n // ignore cache write failures\n }\n }\n\n const fetchModels = async (force: boolean): Promise<{ models: OpenRouterModel[]; lastUpdated: number | null }> => {\n if (!force) {\n const cached = readCache()\n if (cached) return { models: cached.data, lastUpdated: cached.ts }\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${options.apiKey}`,\n ...getRecommendedHeaders(),\n }\n\n const response = await fetcher(endpoint, { headers })\n if (!response.ok) {\n const text = await response.text().catch(() => \"\")\n throw new Error(`Failed to fetch models: ${response.status}${text ? ` - ${text}` : \"\"}`)\n }\n\n const json = (await response.json()) as { data?: OpenRouterModel[] }\n const models = json.data ?? []\n writeCache(models)\n return { models, lastUpdated: Date.now() }\n }\n\n const toResult = (models: OpenRouterModel[], lastUpdated: number | null) => {\n const { categoriesArray } = filterAndGroup(models, { filterToRecommended: false, includeAllCategories: true })\n return { models, categories: categoriesArray, lastUpdated }\n }\n\n return {\n async listModels() {\n const { models, lastUpdated } = await fetchModels(false)\n return toResult(models, lastUpdated)\n },\n async refresh() {\n const { models, lastUpdated } = await fetchModels(true)\n return toResult(models, lastUpdated)\n },\n }\n}\n\n\n"]}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@cappasoft/openrouter-models",
3
+ "version": "1.0.0",
4
+ "description": "Headless client for fetching and managing OpenRouter models",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch"
22
+ },
23
+ "keywords": [
24
+ "openrouter",
25
+ "llm",
26
+ "models",
27
+ "api"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/ecappa/openrouter-model-selector.git",
32
+ "directory": "packages/openrouter-models"
33
+ },
34
+ "license": "MIT",
35
+ "devDependencies": {
36
+ "tsup": "^8.3.5",
37
+ "typescript": "^5.8.3"
38
+ }
39
+ }