language-models 2.0.2 → 2.1.3

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.
@@ -1,5 +1,4 @@
1
-
2
- 
3
- > language-models@2.0.1 build /Users/nathanclevenger/projects/primitives.org.ai/packages/language-models
4
- > tsc -p tsconfig.json && cp -r data dist/
5
-
1
+
2
+ > language-models@2.1.3 build /Users/nathanclevenger/projects/primitives.org.ai/packages/language-models
3
+ > tsc -p tsconfig.json && cp -r data dist/
4
+
@@ -0,0 +1,18 @@
1
+
2
+ > language-models@2.1.1 test /Users/nathanclevenger/projects/primitives.org.ai/packages/language-models
3
+ > vitest
4
+
5
+
6
+ DEV v2.1.9 /Users/nathanclevenger/projects/primitives.org.ai/packages/language-models
7
+
8
+ ✓ src/aliases.test.ts (48 tests) 8ms
9
+ ✓ src/models.test.ts (41 tests) 8ms
10
+ ✓ src/index.test.ts (39 tests) 11ms
11
+
12
+ Test Files 3 passed (3)
13
+ Tests 128 passed (128)
14
+ Start at 06:45:30
15
+ Duration 356ms (transform 238ms, setup 0ms, collect 326ms, tests 28ms, environment 0ms, prepare 112ms)
16
+
17
+ PASS Waiting for file changes...
18
+ press h to show help, press q to quit
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # language-models
2
2
 
3
+ ## 2.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Documentation and testing improvements
8
+ - Add deterministic AI testing suite with self-validating patterns
9
+ - Apply StoryBrand narrative to all package READMEs
10
+ - Update TESTING.md with four principles of deterministic AI testing
11
+ - Fix duplicate examples package name conflict
12
+
13
+ ## 2.1.1
14
+
15
+ ## 2.0.3
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies
20
+ - rpc.do@0.2.0
21
+
3
22
  ## 2.0.2
4
23
 
5
24
  ## 2.0.1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 .org.ai
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 CHANGED
@@ -1,79 +1,140 @@
1
1
  # language-models
2
2
 
3
- Model listing and resolution for LLM providers. Fetches models from OpenRouter and resolves aliases to full model IDs.
3
+ **Stop memorizing model IDs. Start shipping.**
4
+
5
+ You're building AI-powered applications, but every provider has different naming conventions. Is it `claude-opus-4-5-20251101` or `anthropic/claude-opus-4.5`? Was it `gpt-4o` or `openai/gpt-4o`? You shouldn't have to care.
6
+
7
+ ## The Problem
8
+
9
+ ```typescript
10
+ // Without language-models: fragile, provider-specific, constantly breaking
11
+ const model = 'anthropic/claude-3-opus-20240229' // Wait, is this still current?
12
+ const model = 'claude-opus-4-5-20251101' // Or was it this format?
13
+ const model = 'anthropic/claude-opus-4.5' // Which one does OpenRouter want?
14
+ ```
15
+
16
+ ## The Solution
17
+
18
+ ```typescript
19
+ // With language-models: simple, memorable, always resolves correctly
20
+ import { resolve } from 'language-models'
21
+
22
+ resolve('opus') // 'anthropic/claude-opus-4.5'
23
+ resolve('sonnet') // 'anthropic/claude-sonnet-4.5'
24
+ resolve('gpt') // 'openai/gpt-4o'
25
+ resolve('llama') // 'meta-llama/llama-4-maverick'
26
+ ```
4
27
 
5
28
  ## Quick Start
6
29
 
30
+ **1. Install**
31
+ ```bash
32
+ npm install language-models
33
+ ```
34
+
35
+ **2. Import**
7
36
  ```typescript
8
37
  import { resolve, list, search } from 'language-models'
38
+ ```
9
39
 
10
- // Resolve aliases to full model IDs
11
- resolve('opus') // 'anthropic/claude-opus-4.5'
12
- resolve('gpt-4o') // 'openai/gpt-4o'
13
- resolve('llama-70b') // 'meta-llama/llama-3.3-70b-instruct'
14
- resolve('mistral') // 'mistralai/mistral-large-2411'
15
-
16
- // List all available models
17
- const models = list()
40
+ **3. Use**
41
+ ```typescript
42
+ // Resolve human-friendly aliases to full model IDs
43
+ const modelId = resolve('opus')
18
44
 
19
- // Search models
45
+ // Search across 200+ models
20
46
  const claudeModels = search('claude')
47
+
48
+ // Get full model catalog with pricing and context info
49
+ const allModels = list()
21
50
  ```
22
51
 
23
- ## API
52
+ ## API Reference
24
53
 
25
54
  ### `resolve(input: string): string`
26
55
 
27
- Resolve an alias or partial name to a full model ID.
56
+ Resolve an alias or partial name to a full OpenRouter model ID.
28
57
 
29
58
  ```typescript
30
59
  resolve('opus') // 'anthropic/claude-opus-4.5'
31
60
  resolve('sonnet') // 'anthropic/claude-sonnet-4.5'
32
61
  resolve('gpt') // 'openai/gpt-4o'
33
62
  resolve('llama') // 'meta-llama/llama-4-maverick'
34
- resolve('anthropic/claude-opus-4.5') // 'anthropic/claude-opus-4.5' (pass-through)
63
+ resolve('anthropic/claude-opus-4.5') // Pass-through for full IDs
64
+ ```
65
+
66
+ ### `resolveWithProvider(input: string): ResolvedModel`
67
+
68
+ Get full routing information including provider details for direct SDK access.
69
+
70
+ ```typescript
71
+ const info = resolveWithProvider('opus')
72
+ // {
73
+ // id: 'anthropic/claude-opus-4.5',
74
+ // provider: 'anthropic',
75
+ // providerModelId: 'claude-opus-4-5-20251101',
76
+ // supportsDirectRouting: true,
77
+ // model: { name, pricing, context_length, ... }
78
+ // }
35
79
  ```
36
80
 
37
81
  ### `list(): ModelInfo[]`
38
82
 
39
- List all available models from OpenRouter.
83
+ Get the complete model catalog with pricing, context lengths, and capabilities.
40
84
 
41
85
  ### `get(id: string): ModelInfo | undefined`
42
86
 
43
- Get a model by exact ID.
87
+ Fetch a specific model by exact ID.
44
88
 
45
89
  ### `search(query: string): ModelInfo[]`
46
90
 
47
- Search models by ID or name.
48
-
49
- ## Available Aliases
50
-
51
- | Alias | Model ID |
52
- |-------|----------|
53
- | `opus` | anthropic/claude-opus-4.5 |
54
- | `sonnet` | anthropic/claude-sonnet-4.5 |
55
- | `haiku` | anthropic/claude-haiku-4.5 |
56
- | `claude` | anthropic/claude-sonnet-4.5 |
57
- | `gpt`, `gpt-4o`, `4o` | openai/gpt-4o |
58
- | `o1`, `o3`, `o3-mini` | openai/o1, openai/o3, openai/o3-mini |
59
- | `gemini`, `flash` | google/gemini-2.5-flash |
60
- | `gemini-pro` | google/gemini-2.5-pro |
61
- | `llama`, `llama-4` | meta-llama/llama-4-maverick |
62
- | `llama-70b` | meta-llama/llama-3.3-70b-instruct |
63
- | `mistral` | mistralai/mistral-large-2411 |
64
- | `codestral` | mistralai/codestral-2501 |
65
- | `deepseek` | deepseek/deepseek-chat |
66
- | `r1` | deepseek/deepseek-r1 |
67
- | `qwen` | qwen/qwen3-235b-a22b |
68
- | `grok` | x-ai/grok-3 |
69
- | `sonar` | perplexity/sonar-pro |
70
-
71
- ## Updating Models
72
-
73
- Fetch the latest models from OpenRouter:
91
+ Find models matching a search query across IDs and names.
92
+
93
+ ## Supported Aliases
94
+
95
+ | You type | You get |
96
+ |----------|---------|
97
+ | `opus` | `anthropic/claude-opus-4.5` |
98
+ | `sonnet` | `anthropic/claude-sonnet-4.5` |
99
+ | `haiku` | `anthropic/claude-haiku-4.5` |
100
+ | `gpt`, `gpt-4o`, `4o` | `openai/gpt-4o` |
101
+ | `o1`, `o3`, `o3-mini` | `openai/o1`, `openai/o3`, `openai/o3-mini` |
102
+ | `gemini`, `flash` | `google/gemini-2.5-flash` |
103
+ | `gemini-pro` | `google/gemini-2.5-pro` |
104
+ | `llama`, `llama-4` | `meta-llama/llama-4-maverick` |
105
+ | `llama-70b` | `meta-llama/llama-3.3-70b-instruct` |
106
+ | `mistral` | `mistralai/mistral-large-2411` |
107
+ | `codestral` | `mistralai/codestral-2501` |
108
+ | `deepseek` | `deepseek/deepseek-chat` |
109
+ | `r1` | `deepseek/deepseek-r1` |
110
+ | `qwen` | `qwen/qwen3-235b-a22b` |
111
+ | `grok` | `x-ai/grok-3` |
112
+ | `sonar` | `perplexity/sonar-pro` |
113
+
114
+ ## Direct Provider Routing
115
+
116
+ For providers that support direct SDK access (Anthropic, OpenAI, Google), use `resolveWithProvider` to get the native model ID:
117
+
118
+ ```typescript
119
+ import { resolveWithProvider, DIRECT_PROVIDERS } from 'language-models'
120
+
121
+ const { provider, providerModelId, supportsDirectRouting } = resolveWithProvider('opus')
122
+
123
+ if (supportsDirectRouting) {
124
+ // Use native SDK with providerModelId
125
+ } else {
126
+ // Route through OpenRouter
127
+ }
128
+ ```
129
+
130
+ ## Updating the Model Catalog
74
131
 
75
132
  ```bash
76
133
  pnpm fetch-models
77
134
  ```
78
135
 
79
- This updates `data/models.json` with all available models.
136
+ Fetches the latest models from OpenRouter and updates `data/models.json`.
137
+
138
+ ## License
139
+
140
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "language-models",
3
- "version": "2.0.2",
3
+ "version": "2.1.3",
4
4
  "description": "Model listing and resolution for LLM providers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,15 +11,6 @@
11
11
  "types": "./dist/index.d.ts"
12
12
  }
13
13
  },
14
- "scripts": {
15
- "build": "tsc -p tsconfig.json && cp -r data dist/",
16
- "dev": "tsc -p tsconfig.json --watch",
17
- "test": "vitest",
18
- "typecheck": "tsc --noEmit",
19
- "lint": "eslint .",
20
- "clean": "rm -rf dist",
21
- "fetch-models": "npx tsx scripts/fetch-models.ts"
22
- },
23
14
  "keywords": [
24
15
  "ai",
25
16
  "llm",
@@ -28,7 +19,14 @@
28
19
  "primitives"
29
20
  ],
30
21
  "license": "MIT",
31
- "dependencies": {
32
- "rpc.do": "^0.1.0"
22
+ "dependencies": {},
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json && cp -r data dist/",
25
+ "dev": "tsc -p tsconfig.json --watch",
26
+ "test": "vitest",
27
+ "typecheck": "tsc --noEmit",
28
+ "lint": "eslint .",
29
+ "clean": "rm -rf dist",
30
+ "fetch-models": "npx tsx scripts/fetch-models.ts"
33
31
  }
34
- }
32
+ }
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Fetch models from OpenRouter API and save to data/models.json
4
+ *
5
+ * Fetches from both:
6
+ * - /api/v1/models - Basic model info
7
+ * - /api/frontend/models - Provider routing info (provider_model_id, endpoint)
8
+ *
9
+ * Usage: npx tsx scripts/fetch-models.ts
10
+ */
11
+ import { writeFileSync } from 'fs';
12
+ import { dirname, join } from 'path';
13
+ import { fileURLToPath } from 'url';
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const OUTPUT_PATH = join(__dirname, '..', 'data', 'models.json');
16
+ async function fetchModels() {
17
+ console.log('Fetching models from OpenRouter API...');
18
+ // Fetch basic model info
19
+ const modelsResponse = await fetch('https://openrouter.ai/api/v1/models');
20
+ if (!modelsResponse.ok) {
21
+ throw new Error(`Failed to fetch models: ${modelsResponse.status} ${modelsResponse.statusText}`);
22
+ }
23
+ const modelsData = await modelsResponse.json();
24
+ const models = modelsData.data || modelsData;
25
+ console.log(`Found ${models.length} models from v1 API`);
26
+ // Fetch frontend models for provider routing info
27
+ console.log('Fetching provider routing info from frontend API...');
28
+ let frontendModels = [];
29
+ try {
30
+ const frontendResponse = await fetch('https://openrouter.ai/api/frontend/models');
31
+ if (frontendResponse.ok) {
32
+ const frontendData = await frontendResponse.json();
33
+ frontendModels = Array.isArray(frontendData) ? frontendData : (frontendData.data || []);
34
+ console.log(`Found ${frontendModels.length} models from frontend API`);
35
+ }
36
+ }
37
+ catch (err) {
38
+ console.warn('Could not fetch frontend models, continuing without provider routing info');
39
+ }
40
+ // Create a map of frontend models by slug for quick lookup
41
+ const frontendMap = new Map();
42
+ for (const fm of frontendModels) {
43
+ if (fm.slug) {
44
+ frontendMap.set(fm.slug, fm);
45
+ }
46
+ }
47
+ // Merge frontend data into models
48
+ let enrichedCount = 0;
49
+ for (const model of models) {
50
+ const frontend = frontendMap.get(model.id);
51
+ if (frontend?.endpoint) {
52
+ // Add provider routing info from nested endpoint
53
+ if (frontend.endpoint.provider_model_id) {
54
+ model.provider_model_id = frontend.endpoint.provider_model_id;
55
+ }
56
+ if (frontend.endpoint.provider_slug) {
57
+ model.provider = frontend.endpoint.provider_slug;
58
+ }
59
+ if (frontend.endpoint.provider_info?.baseUrl) {
60
+ model.endpoint = {
61
+ baseUrl: frontend.endpoint.provider_info.baseUrl,
62
+ modelId: frontend.endpoint.provider_model_id || model.id.split('/')[1]
63
+ };
64
+ }
65
+ enrichedCount++;
66
+ }
67
+ else {
68
+ // Extract provider from ID as fallback
69
+ const slashIndex = model.id.indexOf('/');
70
+ if (slashIndex > 0) {
71
+ model.provider = model.id.substring(0, slashIndex);
72
+ }
73
+ }
74
+ }
75
+ console.log(`Enriched ${enrichedCount} models with provider routing info`);
76
+ // Sort by created date (newest first)
77
+ models.sort((a, b) => (b.created || 0) - (a.created || 0));
78
+ writeFileSync(OUTPUT_PATH, JSON.stringify(models, null, 2));
79
+ console.log(`Saved to ${OUTPUT_PATH}`);
80
+ // Print some stats
81
+ const providers = new Set(models.map((m) => m.id.split('/')[0]));
82
+ console.log(`\nProviders: ${[...providers].join(', ')}`);
83
+ // Show which direct providers have routing info
84
+ const directProviders = ['openai', 'anthropic', 'google'];
85
+ for (const provider of directProviders) {
86
+ const withRouting = models.filter((m) => m.id.startsWith(`${provider}/`) && m.provider_model_id);
87
+ console.log(`${provider}: ${withRouting.length} models with provider_model_id`);
88
+ }
89
+ }
90
+ fetchModels().catch(console.error);
package/src/aliases.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Model aliases - map simple names to full model IDs
3
+ */
4
+ export const ALIASES = {
5
+ // Claude (Anthropic)
6
+ 'opus': 'anthropic/claude-opus-4.5',
7
+ 'sonnet': 'anthropic/claude-sonnet-4.5',
8
+ 'haiku': 'anthropic/claude-haiku-4.5',
9
+ 'claude': 'anthropic/claude-sonnet-4.5',
10
+ // GPT (OpenAI)
11
+ 'gpt': 'openai/gpt-4o',
12
+ 'gpt-4o': 'openai/gpt-4o',
13
+ 'gpt-4o-mini': 'openai/gpt-4o-mini',
14
+ '4o': 'openai/gpt-4o',
15
+ 'o1': 'openai/o1',
16
+ 'o3': 'openai/o3',
17
+ 'o3-mini': 'openai/o3-mini',
18
+ 'o4-mini': 'openai/o4-mini',
19
+ // Gemini (Google)
20
+ 'gemini': 'google/gemini-2.5-flash',
21
+ 'flash': 'google/gemini-2.5-flash',
22
+ 'gemini-flash': 'google/gemini-2.5-flash',
23
+ 'gemini-pro': 'google/gemini-2.5-pro',
24
+ // Llama (Meta)
25
+ 'llama': 'meta-llama/llama-4-maverick',
26
+ 'llama-4': 'meta-llama/llama-4-maverick',
27
+ 'llama-70b': 'meta-llama/llama-3.3-70b-instruct',
28
+ // DeepSeek
29
+ 'deepseek': 'deepseek/deepseek-chat',
30
+ 'r1': 'deepseek/deepseek-r1',
31
+ // Mistral
32
+ 'mistral': 'mistralai/mistral-large-2411',
33
+ 'codestral': 'mistralai/codestral-2501',
34
+ // Qwen
35
+ 'qwen': 'qwen/qwen3-235b-a22b',
36
+ // Grok
37
+ 'grok': 'x-ai/grok-3',
38
+ // Perplexity
39
+ 'sonar': 'perplexity/sonar-pro',
40
+ };