failsafe-llm-model-resolver 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.
Files changed (5) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +194 -0
  3. package/index.d.ts +67 -0
  4. package/index.js +272 -0
  5. package/package.json +46 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # failsafe-llm-model-resolver
2
+
3
+ **Self-healing, failsafe resolver for the current frontier model.**
4
+
5
+ Live `/models` fetch → preference rules → env-pinned fallback.
6
+ A transient network hiccup never kills a batch job.
7
+
8
+ ```js
9
+ const { resolveModel, getNextFreeModel } = require('failsafe-llm-model-resolver');
10
+
11
+ const { id, source } = await resolveModel('xai', process.env.XAI_API_KEY);
12
+ // → { id: 'grok-4', source: 'live' }
13
+ // or { id: 'grok-4', source: 'fallback' } when the live list is unreachable
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Why this exists
19
+
20
+ Hard-coding model IDs is fragile. Providers ship new versions weekly; free tiers rotate; rate-limits appear and disappear. The classic pattern:
21
+
22
+ ```js
23
+ const model = 'grok-4'; // stale next month
24
+ const model = 'openrouter/free'; // may 404 or be rate-limited
25
+ ```
26
+
27
+ …breaks batch jobs and agents at the worst moment.
28
+
29
+ **failsafe-llm-model-resolver** does the boring but critical work once:
30
+
31
+ 1. Hits each provider’s native model-list endpoint
32
+ 2. Applies a stable preference rule (newest non-fast / non-mini / non-haiku)
33
+ 3. Caches the result for the process lifetime
34
+ 4. Falls back to an env-configurable pinned ID if anything fails
35
+
36
+ You get the current frontier model when the network works, and a known-good model when it doesn’t.
37
+
38
+ ---
39
+
40
+ ## Supported providers
41
+
42
+ | Provider | Endpoint | Preference rule |
43
+ |----------------|-------------------------------------------------------|------------------------------------------------------|
44
+ | **xAI / Grok** | `GET https://api.x.ai/v1/models` | newest `grok-*`, skip `fast` / `mini` / `lite` |
45
+ | **Anthropic** | `GET https://api.anthropic.com/v1/models` | newest `claude-*`, skip `haiku` / `instant` (newest-first) |
46
+ | **Gemini** | `GET …/v1beta/models?key=…` | newest non-`flash` / non-`lite` |
47
+ | **OpenRouter** | `GET https://openrouter.ai/api/v1/models` | free models (`pricing.prompt/completion === "0"`) + round-robin |
48
+
49
+ No heavy SDKs. Native `fetch` only (Node ≥ 18).
50
+
51
+ ---
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ npm install failsafe-llm-model-resolver
57
+ ```
58
+
59
+ Or, for personal multi-project reuse without publishing:
60
+
61
+ ```bash
62
+ # place index.js somewhere shared, e.g. ~/dev/shared-lib/failsafe-llm-model-resolver/
63
+ export NODE_PATH="$HOME/dev/shared-lib:$NODE_PATH"
64
+ # then: require('failsafe-llm-model-resolver')
65
+ ```
66
+
67
+ ---
68
+
69
+ ## API
70
+
71
+ ### `resolveModel(provider, apiKey, options?)`
72
+
73
+ ```ts
74
+ type Provider = 'xai' | 'anthropic' | 'gemini' | 'openrouter';
75
+
76
+ interface ResolveResult {
77
+ id: string; // ready to pass to the provider’s chat endpoint
78
+ source: 'live' | 'fallback';
79
+ }
80
+
81
+ function resolveModel(
82
+ provider: Provider,
83
+ apiKey: string | null | undefined,
84
+ options?: { forceRefresh?: boolean; fallback?: string }
85
+ ): Promise<ResolveResult>;
86
+ ```
87
+
88
+ - **Paid providers** → live list + preference rule → single best ID
89
+ - **OpenRouter** → first currently free model (use `getNextFreeModel` for rotation)
90
+ - **Any failure / missing key** → env-pinned fallback, `source: 'fallback'`
91
+
92
+ ### OpenRouter free-tier helpers
93
+
94
+ Drop-in replacements for the classic `fetchOrFreeModels` / `getNextOrModel` pattern:
95
+
96
+ ```js
97
+ const free = await fetchFreeModels(process.env.OPENROUTER_API_KEY); // Set<string>
98
+ const model = await getNextFreeModel(process.env.OPENROUTER_API_KEY); // round-robin
99
+ resetFreeModelIndex(); // call at the start of a batch if desired
100
+ ```
101
+
102
+ ### Cache control
103
+
104
+ ```js
105
+ clearCache(); // wipe all in-memory caches (mainly for tests)
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Env-configurable fallbacks
111
+
112
+ Never let a transient error kill a job. Set any of:
113
+
114
+ ```bash
115
+ XAI_MODEL_FALLBACK=grok-4
116
+ # or GROK_MODEL_FALLBACK=grok-4
117
+
118
+ ANTHROPIC_MODEL_FALLBACK=claude-sonnet-4-20250514
119
+ GEMINI_MODEL_FALLBACK=gemini-2.5-pro
120
+ OPENROUTER_MODEL_FALLBACK=openrouter/free
121
+ # or OPENROUTER_FREE_FALLBACK=openrouter/free
122
+ ```
123
+
124
+ Defaults are sensible current frontier IDs; override them for your own pins.
125
+
126
+ ---
127
+
128
+ ## Quick examples
129
+
130
+ **Simple resolve**
131
+
132
+ ```js
133
+ const { resolveModel } = require('failsafe-llm-model-resolver');
134
+
135
+ async function chat(prompt) {
136
+ const { id } = await resolveModel('anthropic', process.env.ANTHROPIC_API_KEY);
137
+ // use `id` with the Anthropic SDK / fetch …
138
+ }
139
+ ```
140
+
141
+ **Self-healing OpenRouter free rotation** (matches the original working code)
142
+
143
+ ```js
144
+ const { getNextFreeModel, resetFreeModelIndex } = require('failsafe-llm-model-resolver');
145
+
146
+ async function callOpenRouter(prompt) {
147
+ resetFreeModelIndex();
148
+ let lastErr;
149
+ for (let attempt = 0; attempt < 10; attempt++) {
150
+ const model = await getNextFreeModel(process.env.OPENROUTER_API_KEY);
151
+ try {
152
+ // … POST /api/v1/chat/completions with model …
153
+ return result;
154
+ } catch (e) {
155
+ lastErr = e; // rate-limit / empty → try next free model
156
+ }
157
+ }
158
+ throw lastErr;
159
+ }
160
+ ```
161
+
162
+ **Force a refresh**
163
+
164
+ ```js
165
+ const { id } = await resolveModel('xai', key, { forceRefresh: true });
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Design notes
171
+
172
+ - **In-memory cache** – one fetch per provider per process. Matches the proven `_orFreeModels` pattern.
173
+ - **Preference, not exact IDs** – paid providers use regex + “first match after newest-first”. When the next `grok-5` or `claude-sonnet-5` ships, you get it automatically.
174
+ - **OpenRouter free is special** – exact free filter + ordered round-robin. The free pool rotates; the code adapts.
175
+ - **Fail-closed to a known model** – network, 429 on the models endpoint, empty list, missing key → pinned fallback. Batch jobs keep running.
176
+ - **Zero dependencies** – only what Node 18+ already provides.
177
+
178
+ ---
179
+
180
+ ## Smoke test
181
+
182
+ ```bash
183
+ npm test
184
+ # or with live keys:
185
+ XAI_API_KEY=… ANTHROPIC_API_KEY=… GEMINI_API_KEY=… OPENROUTER_API_KEY=… npm test
186
+ ```
187
+
188
+ Without keys the module correctly returns every fallback (`source: 'fallback'`).
189
+
190
+ ---
191
+
192
+ ## License
193
+
194
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * self-healing-models
3
+ *
4
+ * Self-healing, failsafe resolver for the current frontier model.
5
+ */
6
+
7
+ export type Provider = 'xai' | 'anthropic' | 'gemini' | 'openrouter';
8
+
9
+ export type ResolveSource = 'live' | 'fallback';
10
+
11
+ export interface ResolveResult {
12
+ /** Model ID ready to pass to the provider's chat/completions endpoint */
13
+ id: string;
14
+ /** Whether the ID came from a live /models fetch or from a pinned fallback */
15
+ source: ResolveSource;
16
+ }
17
+
18
+ export interface ResolveOptions {
19
+ /** Bypass the in-memory process cache */
20
+ forceRefresh?: boolean;
21
+ /** Override the env/hard-coded fallback for this call only */
22
+ fallback?: string;
23
+ }
24
+
25
+ /**
26
+ * Resolve the best model ID for a provider.
27
+ *
28
+ * Paid providers use live /models + preference rules (newest non-fast/non-mini).
29
+ * OpenRouter returns the first currently free model.
30
+ * On any failure (network, auth, empty list) returns the env-configured fallback
31
+ * so a transient hiccup never kills a batch job.
32
+ */
33
+ export function resolveModel(
34
+ provider: Provider,
35
+ apiKey: string | undefined | null,
36
+ options?: ResolveOptions
37
+ ): Promise<ResolveResult>;
38
+
39
+ /**
40
+ * Fetch (and cache) the set of currently free OpenRouter model IDs.
41
+ * Drop-in for the classic fetchOrFreeModels() pattern.
42
+ */
43
+ export function fetchFreeModels(
44
+ apiKey: string,
45
+ forceRefresh?: boolean
46
+ ): Promise<Set<string>>;
47
+
48
+ /**
49
+ * Round-robin through currently available OpenRouter free models.
50
+ * Drop-in for the classic getNextOrModel() pattern.
51
+ */
52
+ export function getNextFreeModel(apiKey: string): Promise<string>;
53
+
54
+ /** Reset the free-model round-robin index (call at the start of a batch). */
55
+ export function resetFreeModelIndex(): void;
56
+
57
+ /** Clear all in-memory caches (mainly for tests). */
58
+ export function clearCache(): void;
59
+
60
+ export const DEFAULT_FALLBACKS: Readonly<Record<Provider, string>>;
61
+
62
+ export const PREFERENCE: Readonly<
63
+ Record<
64
+ Exclude<Provider, 'openrouter'>,
65
+ { include: RegExp; exclude: RegExp }
66
+ >
67
+ >;
package/index.js ADDED
@@ -0,0 +1,272 @@
1
+ /**
2
+ * failsafe-llm-model-resolver
3
+ *
4
+ * Self-healing, failsafe resolver for the current frontier model across
5
+ * xAI, Anthropic, Gemini, and OpenRouter.
6
+ *
7
+ * resolveModel(provider, apiKey, options?) → Promise<{ id, source: 'live'|'fallback' }>
8
+ *
9
+ * Design goals
10
+ * ────────────
11
+ * • Live fetch from each provider’s native /models endpoint
12
+ * • Preference rules:
13
+ * – paid providers: regex + “newest first, skip fast/mini/lite/haiku”
14
+ * – OpenRouter free: filter pricing.prompt/completion === '0', then round-robin
15
+ * • In-memory cache per process
16
+ * • Env-configurable pinned fallbacks so a transient network blip never kills a batch job
17
+ *
18
+ * @see https://github.com/prakar/failsafe-llm-model-resolver
19
+ */
20
+
21
+ 'use strict';
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // In-memory caches (process lifetime)
25
+ // ---------------------------------------------------------------------------
26
+ const _cache = {
27
+ xai: null,
28
+ anthropic: null,
29
+ gemini: null,
30
+ openrouter: null, // full free-model Set
31
+ };
32
+ let _orFreeIdx = 0; // round-robin index for OpenRouter free models
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Defaults / env fallbacks
36
+ // ---------------------------------------------------------------------------
37
+ const DEFAULT_FALLBACKS = {
38
+ xai: process.env.XAI_MODEL_FALLBACK || process.env.GROK_MODEL_FALLBACK || 'grok-4',
39
+ anthropic: process.env.ANTHROPIC_MODEL_FALLBACK || 'claude-sonnet-4-20250514',
40
+ gemini: process.env.GEMINI_MODEL_FALLBACK || 'gemini-2.5-pro',
41
+ openrouter: process.env.OPENROUTER_MODEL_FALLBACK || process.env.OPENROUTER_FREE_FALLBACK || 'openrouter/free',
42
+ };
43
+
44
+ // Preference patterns for paid providers (applied after newest-first ordering)
45
+ const PREFERENCE = {
46
+ // Prefer flagship grok-*, skip fast / mini / code-fast variants when possible
47
+ xai: {
48
+ include: /^grok-/i,
49
+ exclude: /fast|mini|lite|code-fast/i,
50
+ },
51
+ // Prefer sonnet/opus over haiku; newest first already gives us the latest
52
+ anthropic: {
53
+ include: /^claude-/i,
54
+ exclude: /haiku|instant|mini/i,
55
+ },
56
+ // Prefer pro / non-flash / non-lite when available
57
+ gemini: {
58
+ include: /gemini-/i,
59
+ exclude: /flash|lite|embedding|aqa|imagen|veo|tts|live/i,
60
+ },
61
+ };
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Tiny fetch helper (native fetch, Node 18+)
65
+ // ---------------------------------------------------------------------------
66
+ async function httpGet(url, headers = {}) {
67
+ const res = await fetch(url, {
68
+ method: 'GET',
69
+ headers: {
70
+ 'Accept': 'application/json',
71
+ ...headers,
72
+ },
73
+ });
74
+ if (!res.ok) {
75
+ const body = await res.text().catch(() => '');
76
+ throw new Error(`HTTP ${res.status} ${res.statusText}: ${body.slice(0, 200)}`);
77
+ }
78
+ return res.json();
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Per-provider fetch + normalize → string[] of usable model IDs (newest first)
83
+ // ---------------------------------------------------------------------------
84
+ async function fetchXaiModels(apiKey) {
85
+ const json = await httpGet('https://api.x.ai/v1/models', {
86
+ Authorization: `Bearer ${apiKey}`,
87
+ });
88
+ // OpenAI-compatible shape: { data: [ { id, ... } ] }
89
+ const list = (json.data || json.models || []).map(m => m.id || m.name).filter(Boolean);
90
+ return list;
91
+ }
92
+
93
+ async function fetchAnthropicModels(apiKey) {
94
+ const json = await httpGet('https://api.anthropic.com/v1/models', {
95
+ 'x-api-key': apiKey,
96
+ 'anthropic-version': '2023-06-01',
97
+ });
98
+ // Newest-first by design. Shape: { data: [ { id, display_name, created_at } ] }
99
+ const list = (json.data || []).map(m => m.id).filter(Boolean);
100
+ return list;
101
+ }
102
+
103
+ async function fetchGeminiModels(apiKey) {
104
+ // Gemini uses query-string key and a different envelope
105
+ const url = `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(apiKey)}&pageSize=1000`;
106
+ const json = await httpGet(url);
107
+ // Shape: { models: [ { name: "models/gemini-…", … } ] }
108
+ // The ID used in generateContent is the part after "models/"
109
+ const list = (json.models || [])
110
+ .map(m => {
111
+ const name = m.name || '';
112
+ return name.startsWith('models/') ? name.slice(7) : name;
113
+ })
114
+ .filter(id => id && /generateContent/i.test((m => (m.supportedGenerationMethods || m.supported_actions || []).join(','))(m) || 'generateContent'))
115
+ .filter(Boolean);
116
+ return list;
117
+ }
118
+
119
+ async function fetchOpenRouterFreeModels(apiKey) {
120
+ const json = await httpGet('https://openrouter.ai/api/v1/models', {
121
+ Authorization: `Bearer ${apiKey}`,
122
+ });
123
+ // Free = pricing.prompt === "0" && pricing.completion === "0" (string or number)
124
+ const isZero = v => v === 0 || v === '0' || v === '0.0' || v === '0.00';
125
+ const free = (json.data || [])
126
+ .filter(m => m.pricing && isZero(m.pricing.prompt) && isZero(m.pricing.completion))
127
+ .map(m => m.id)
128
+ .filter(Boolean);
129
+ return free;
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Preference selector (paid providers)
134
+ // ---------------------------------------------------------------------------
135
+ function pickPreferred(ids, rules) {
136
+ if (!ids || ids.length === 0) return null;
137
+
138
+ // 1. Prefer models that match include and do NOT match exclude
139
+ const preferred = ids.filter(id =>
140
+ rules.include.test(id) && !rules.exclude.test(id)
141
+ );
142
+ if (preferred.length) return preferred[0];
143
+
144
+ // 2. Fall back to any that match include (even if they hit exclude)
145
+ const anyInclude = ids.filter(id => rules.include.test(id));
146
+ if (anyInclude.length) return anyInclude[0];
147
+
148
+ // 3. Absolute last resort: first ID in the list
149
+ return ids[0];
150
+ }
151
+
152
+ // ---------------------------------------------------------------------------
153
+ // Public API
154
+ // ---------------------------------------------------------------------------
155
+
156
+ /**
157
+ * Resolve the best model ID for a provider.
158
+ *
159
+ * @param {'xai'|'anthropic'|'gemini'|'openrouter'} provider
160
+ * @param {string} apiKey
161
+ * @param {object} [options]
162
+ * @param {boolean} [options.forceRefresh=false] Bypass cache
163
+ * @param {string} [options.fallback] Override env fallback for this call
164
+ * @returns {Promise<{ id: string, source: 'live'|'fallback' }>}
165
+ */
166
+ async function resolveModel(provider, apiKey, options = {}) {
167
+ const p = String(provider || '').toLowerCase();
168
+ if (!['xai', 'anthropic', 'gemini', 'openrouter'].includes(p)) {
169
+ throw new Error(`Unknown provider: ${provider}. Expected xai|anthropic|gemini|openrouter`);
170
+ }
171
+ if (!apiKey) {
172
+ // No key → immediate fallback (still useful for offline / test runs)
173
+ return { id: options.fallback || DEFAULT_FALLBACKS[p], source: 'fallback' };
174
+ }
175
+
176
+ const force = !!options.forceRefresh;
177
+ const pinned = options.fallback || DEFAULT_FALLBACKS[p];
178
+
179
+ try {
180
+ let id;
181
+
182
+ if (p === 'openrouter') {
183
+ // For the generic resolve we return the first free model (or the fallback).
184
+ // Callers that want round-robin should use getNextFreeModel().
185
+ const free = await _getOpenRouterFreeSet(apiKey, force);
186
+ id = free.size ? Array.from(free)[0] : null;
187
+ } else {
188
+ let list = _cache[p];
189
+ if (!list || force) {
190
+ if (p === 'xai') list = await fetchXaiModels(apiKey);
191
+ if (p === 'anthropic') list = await fetchAnthropicModels(apiKey);
192
+ if (p === 'gemini') list = await fetchGeminiModels(apiKey);
193
+ _cache[p] = list;
194
+ }
195
+ id = pickPreferred(list, PREFERENCE[p]);
196
+ }
197
+
198
+ if (id) return { id, source: 'live' };
199
+ return { id: pinned, source: 'fallback' };
200
+ } catch (err) {
201
+ // Never let a transient network error kill a batch job
202
+ return { id: pinned, source: 'fallback' };
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Return the cached (or freshly fetched) Set of OpenRouter free model IDs.
208
+ * Matches the old fetchOrFreeModels() contract.
209
+ */
210
+ async function fetchFreeModels(apiKey, forceRefresh = false) {
211
+ return _getOpenRouterFreeSet(apiKey, forceRefresh);
212
+ }
213
+
214
+ async function _getOpenRouterFreeSet(apiKey, force = false) {
215
+ if (_cache.openrouter && !force) return _cache.openrouter;
216
+ try {
217
+ const ids = await fetchOpenRouterFreeModels(apiKey);
218
+ _cache.openrouter = new Set(ids);
219
+ return _cache.openrouter;
220
+ } catch (e) {
221
+ // Preserve previous cache if any; otherwise seed with the hard-coded fallback
222
+ if (!_cache.openrouter) {
223
+ _cache.openrouter = new Set([DEFAULT_FALLBACKS.openrouter]);
224
+ }
225
+ return _cache.openrouter;
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Round-robin through currently available OpenRouter free models.
231
+ * Drop-in replacement for the legacy getNextOrModel().
232
+ */
233
+ async function getNextFreeModel(apiKey) {
234
+ const available = await _getOpenRouterFreeSet(apiKey);
235
+ if (available.size === 0) return DEFAULT_FALLBACKS.openrouter;
236
+ const models = Array.from(available);
237
+ const model = models[_orFreeIdx % models.length];
238
+ _orFreeIdx = (_orFreeIdx + 1) % models.length;
239
+ return model;
240
+ }
241
+
242
+ /**
243
+ * Reset the free-model round-robin index (useful at the start of a batch).
244
+ */
245
+ function resetFreeModelIndex() {
246
+ _orFreeIdx = 0;
247
+ }
248
+
249
+ /**
250
+ * Clear all in-memory caches (mainly for tests).
251
+ */
252
+ function clearCache() {
253
+ _cache.xai = null;
254
+ _cache.anthropic = null;
255
+ _cache.gemini = null;
256
+ _cache.openrouter = null;
257
+ _orFreeIdx = 0;
258
+ }
259
+
260
+ // ---------------------------------------------------------------------------
261
+ // Exports
262
+ // ---------------------------------------------------------------------------
263
+ module.exports = {
264
+ resolveModel,
265
+ fetchFreeModels,
266
+ getNextFreeModel,
267
+ resetFreeModelIndex,
268
+ clearCache,
269
+ // exposed for advanced callers / tests
270
+ DEFAULT_FALLBACKS,
271
+ PREFERENCE,
272
+ };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "failsafe-llm-model-resolver",
3
+ "version": "1.0.0",
4
+ "description": "Self-healing, failsafe resolver for the current frontier model across xAI, Anthropic, Gemini, and OpenRouter. Live /models fetch + preference rules + env fallbacks so a network blip never kills a batch job.",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "scripts": {
8
+ "test": "node smoke-test.js",
9
+ "smoke": "node smoke-test.js"
10
+ },
11
+ "keywords": [
12
+ "llm",
13
+ "models",
14
+ "openai",
15
+ "anthropic",
16
+ "claude",
17
+ "gemini",
18
+ "xai",
19
+ "grok",
20
+ "openrouter",
21
+ "self-healing",
22
+ "failover",
23
+ "fallback",
24
+ "model-resolver",
25
+ "frontier"
26
+ ],
27
+ "author": "",
28
+ "license": "MIT",
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/prakar/failsafe-llm-model-resolver.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/prakar/failsafe-llm-model-resolver/issues"
38
+ },
39
+ "homepage": "https://github.com/prakar/failsafe-llm-model-resolver#readme",
40
+ "files": [
41
+ "index.js",
42
+ "index.d.ts",
43
+ "README.md",
44
+ "LICENSE"
45
+ ]
46
+ }