pi-tensorix-provider 1.0.2
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/.github/FUNDING.yml +4 -0
- package/AGENTS.md +56 -0
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/custom-models.json +1 -0
- package/index.ts +329 -0
- package/models.json +562 -0
- package/package.json +34 -0
- package/patch.json +223 -0
- package/scripts/update-models.js +295 -0
package/patch.json
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
{
|
|
2
|
+
"deepseek/deepseek-r1-0528": {
|
|
3
|
+
"reasoning": true,
|
|
4
|
+
"compat": {
|
|
5
|
+
"thinkingFormat": "openai",
|
|
6
|
+
"supportsDeveloperRole": true,
|
|
7
|
+
"supportsReasoningEffort": true
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"deepseek/deepseek-v3.2": {
|
|
11
|
+
"reasoning": true,
|
|
12
|
+
"contextWindow": 163840,
|
|
13
|
+
"maxTokens": 163840,
|
|
14
|
+
"compat": {
|
|
15
|
+
"thinkingFormat": "openai",
|
|
16
|
+
"supportsDeveloperRole": true,
|
|
17
|
+
"supportsReasoningEffort": true
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"deepseek/deepseek-v4-flash": {
|
|
21
|
+
"reasoning": true,
|
|
22
|
+
"contextWindow": 1000000,
|
|
23
|
+
"maxTokens": 131072,
|
|
24
|
+
"compat": {
|
|
25
|
+
"thinkingFormat": "qwen-chat-template",
|
|
26
|
+
"requiresReasoningContentOnAssistantMessages": true,
|
|
27
|
+
"supportsDeveloperRole": true,
|
|
28
|
+
"supportsReasoningEffort": true
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"deepseek/deepseek-v4-pro": {
|
|
32
|
+
"reasoning": true,
|
|
33
|
+
"contextWindow": 1000000,
|
|
34
|
+
"maxTokens": 131072,
|
|
35
|
+
"compat": {
|
|
36
|
+
"thinkingFormat": "openai",
|
|
37
|
+
"supportsDeveloperRole": true,
|
|
38
|
+
"supportsReasoningEffort": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"z-ai/glm-4.7": {
|
|
42
|
+
"reasoning": true,
|
|
43
|
+
"contextWindow": 202752,
|
|
44
|
+
"maxTokens": 131072,
|
|
45
|
+
"compat": {
|
|
46
|
+
"thinkingFormat": "openai",
|
|
47
|
+
"supportsDeveloperRole": true,
|
|
48
|
+
"supportsReasoningEffort": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"z-ai/glm-5": {
|
|
52
|
+
"reasoning": true,
|
|
53
|
+
"contextWindow": 202752,
|
|
54
|
+
"maxTokens": 131072,
|
|
55
|
+
"compat": {
|
|
56
|
+
"thinkingFormat": "openai",
|
|
57
|
+
"supportsDeveloperRole": true,
|
|
58
|
+
"supportsReasoningEffort": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"z-ai/glm-5-turbo": {
|
|
62
|
+
"reasoning": true,
|
|
63
|
+
"contextWindow": 202752,
|
|
64
|
+
"maxTokens": 131072,
|
|
65
|
+
"compat": {
|
|
66
|
+
"thinkingFormat": "openai",
|
|
67
|
+
"supportsDeveloperRole": true,
|
|
68
|
+
"supportsReasoningEffort": true
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"z-ai/glm-5.1": {
|
|
72
|
+
"reasoning": true,
|
|
73
|
+
"contextWindow": 202752,
|
|
74
|
+
"maxTokens": 131072,
|
|
75
|
+
"compat": {
|
|
76
|
+
"thinkingFormat": "openai",
|
|
77
|
+
"supportsDeveloperRole": true,
|
|
78
|
+
"supportsReasoningEffort": true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"z-ai/glm-5v-turbo": {
|
|
82
|
+
"reasoning": true,
|
|
83
|
+
"input": ["text", "image"],
|
|
84
|
+
"contextWindow": 202752,
|
|
85
|
+
"maxTokens": 131072,
|
|
86
|
+
"compat": {
|
|
87
|
+
"thinkingFormat": "openai",
|
|
88
|
+
"supportsDeveloperRole": true,
|
|
89
|
+
"supportsReasoningEffort": true
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"minimax/minimax-m2.5": {
|
|
93
|
+
"reasoning": true,
|
|
94
|
+
"contextWindow": 204800,
|
|
95
|
+
"maxTokens": 131072,
|
|
96
|
+
"compat": {
|
|
97
|
+
"thinkingFormat": "openai",
|
|
98
|
+
"supportsDeveloperRole": true,
|
|
99
|
+
"supportsReasoningEffort": true
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"minimax/minimax-m2.7": {
|
|
103
|
+
"reasoning": true,
|
|
104
|
+
"contextWindow": 204800,
|
|
105
|
+
"maxTokens": 131072,
|
|
106
|
+
"compat": {
|
|
107
|
+
"thinkingFormat": "openai",
|
|
108
|
+
"supportsDeveloperRole": true,
|
|
109
|
+
"supportsReasoningEffort": true
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"moonshotai/kimi-k2.5": {
|
|
113
|
+
"reasoning": true,
|
|
114
|
+
"input": ["text", "image"],
|
|
115
|
+
"contextWindow": 262144,
|
|
116
|
+
"maxTokens": 262144,
|
|
117
|
+
"compat": {
|
|
118
|
+
"thinkingFormat": "openai",
|
|
119
|
+
"supportsDeveloperRole": true,
|
|
120
|
+
"supportsReasoningEffort": true
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"moonshotai/kimi-k2.6": {
|
|
124
|
+
"reasoning": true,
|
|
125
|
+
"input": ["text", "image"],
|
|
126
|
+
"contextWindow": 262144,
|
|
127
|
+
"maxTokens": 262144,
|
|
128
|
+
"compat": {
|
|
129
|
+
"thinkingFormat": "openai",
|
|
130
|
+
"supportsDeveloperRole": true,
|
|
131
|
+
"supportsReasoningEffort": true
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"moonshotai/Kimi-K2.6": {
|
|
135
|
+
"reasoning": true,
|
|
136
|
+
"input": ["text", "image"],
|
|
137
|
+
"contextWindow": 262144,
|
|
138
|
+
"maxTokens": 262144,
|
|
139
|
+
"compat": {
|
|
140
|
+
"thinkingFormat": "openai",
|
|
141
|
+
"supportsDeveloperRole": true,
|
|
142
|
+
"supportsReasoningEffort": true
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"qwen/qwen3-235b-a22b-2507": {
|
|
146
|
+
"reasoning": true,
|
|
147
|
+
"contextWindow": 262144,
|
|
148
|
+
"maxTokens": 262144,
|
|
149
|
+
"compat": {
|
|
150
|
+
"thinkingFormat": "openai",
|
|
151
|
+
"supportsDeveloperRole": true,
|
|
152
|
+
"supportsReasoningEffort": true
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"qwen/qwen3-coder-30b-a3b-instruct": {
|
|
156
|
+
"reasoning": true,
|
|
157
|
+
"contextWindow": 262144,
|
|
158
|
+
"maxTokens": 262144,
|
|
159
|
+
"compat": {
|
|
160
|
+
"thinkingFormat": "openai",
|
|
161
|
+
"supportsDeveloperRole": true,
|
|
162
|
+
"supportsReasoningEffort": true
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"qwen/qwen3-vl-235b-a22b-instruct": {
|
|
166
|
+
"reasoning": true,
|
|
167
|
+
"input": ["text", "image"],
|
|
168
|
+
"contextWindow": 262144,
|
|
169
|
+
"maxTokens": 262144,
|
|
170
|
+
"compat": {
|
|
171
|
+
"thinkingFormat": "openai",
|
|
172
|
+
"supportsDeveloperRole": true,
|
|
173
|
+
"supportsReasoningEffort": true
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"qwen/qwen3.5-122b-a10b": {
|
|
177
|
+
"reasoning": true,
|
|
178
|
+
"contextWindow": 262144,
|
|
179
|
+
"maxTokens": 262144,
|
|
180
|
+
"compat": {
|
|
181
|
+
"thinkingFormat": "openai",
|
|
182
|
+
"supportsDeveloperRole": true,
|
|
183
|
+
"supportsReasoningEffort": true
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"qwen/qwen3.5-9b": {
|
|
187
|
+
"reasoning": true,
|
|
188
|
+
"contextWindow": 262144,
|
|
189
|
+
"maxTokens": 262144,
|
|
190
|
+
"compat": {
|
|
191
|
+
"thinkingFormat": "openai",
|
|
192
|
+
"supportsDeveloperRole": true,
|
|
193
|
+
"supportsReasoningEffort": true
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
"openai/gpt-oss-120b": {
|
|
197
|
+
"reasoning": true,
|
|
198
|
+
"contextWindow": 131072,
|
|
199
|
+
"maxTokens": 32768,
|
|
200
|
+
"compat": {
|
|
201
|
+
"supportsDeveloperRole": true,
|
|
202
|
+
"supportsReasoningEffort": true
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"openai/gpt-oss-20b": {
|
|
206
|
+
"reasoning": true,
|
|
207
|
+
"contextWindow": 131072,
|
|
208
|
+
"maxTokens": 32768,
|
|
209
|
+
"compat": {
|
|
210
|
+
"supportsDeveloperRole": true,
|
|
211
|
+
"supportsReasoningEffort": true
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
"meta-llama/llama-4-maverick": {
|
|
215
|
+
"contextWindow": 1048576,
|
|
216
|
+
"maxTokens": 131072,
|
|
217
|
+
"reasoning": false
|
|
218
|
+
},
|
|
219
|
+
"nvidia/nemotron-3-super-120b-a12b": {
|
|
220
|
+
"contextWindow": 4096,
|
|
221
|
+
"maxTokens": 4096
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Update Tensorix models from API
|
|
4
|
+
*
|
|
5
|
+
* Fetches models from https://api.tensorix.ai/v1/models and updates:
|
|
6
|
+
* - models.json: API model definitions with preserved curated data
|
|
7
|
+
* - README.md: Model table with patch.json overrides applied
|
|
8
|
+
*
|
|
9
|
+
* The Tensorix /v1/models API returns minimal metadata (id only — no pricing,
|
|
10
|
+
* context_length, or reasoning flags). models.json preserves curated specs for
|
|
11
|
+
* known models and creates skeleton entries for new ones. patch.json corrects
|
|
12
|
+
* reasoning capability and adds compatibility overrides at runtime.
|
|
13
|
+
*
|
|
14
|
+
* Merge order for README: models.json → apply patch.json → merge custom-models.json
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
|
|
21
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
|
|
23
|
+
const MODELS_API_URL = 'https://api.tensorix.ai/v1/models';
|
|
24
|
+
const MODELS_JSON_PATH = path.join(__dirname, '..', 'models.json');
|
|
25
|
+
const PATCH_JSON_PATH = path.join(__dirname, '..', 'patch.json');
|
|
26
|
+
const CUSTOM_MODELS_JSON_PATH = path.join(__dirname, '..', 'custom-models.json');
|
|
27
|
+
const README_PATH = path.join(__dirname, '..', 'README.md');
|
|
28
|
+
|
|
29
|
+
const MODELS_TO_SKIP = new Set([
|
|
30
|
+
'qwen/qwen3-embedding-8b',
|
|
31
|
+
'Systran/faster-whisper-large-v3',
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
// ─── Patch application ────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
function applyPatch(model, patch) {
|
|
37
|
+
const result = { ...model };
|
|
38
|
+
|
|
39
|
+
if (patch.name !== undefined) result.name = patch.name;
|
|
40
|
+
if (patch.reasoning !== undefined) result.reasoning = patch.reasoning;
|
|
41
|
+
if (patch.input !== undefined) result.input = patch.input;
|
|
42
|
+
if (patch.contextWindow !== undefined) result.contextWindow = patch.contextWindow;
|
|
43
|
+
if (patch.maxTokens !== undefined) result.maxTokens = patch.maxTokens;
|
|
44
|
+
if (patch.thinkingLevelMap !== undefined) result.thinkingLevelMap = { ...patch.thinkingLevelMap };
|
|
45
|
+
|
|
46
|
+
if (patch.cost) {
|
|
47
|
+
result.cost = {
|
|
48
|
+
input: patch.cost.input ?? result.cost.input,
|
|
49
|
+
output: patch.cost.output ?? result.cost.output,
|
|
50
|
+
cacheRead: patch.cost.cacheRead ?? result.cost.cacheRead,
|
|
51
|
+
cacheWrite: patch.cost.cacheWrite ?? result.cost.cacheWrite,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (patch.compat) {
|
|
55
|
+
result.compat = { ...(result.compat || {}), ...patch.compat };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!result.reasoning && result.compat?.thinkingFormat) {
|
|
59
|
+
delete result.compat.thinkingFormat;
|
|
60
|
+
}
|
|
61
|
+
if (result.compat && Object.keys(result.compat).length === 0) {
|
|
62
|
+
delete result.compat;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildModels(baseModels, customModels, patchData) {
|
|
69
|
+
const modelMap = new Map();
|
|
70
|
+
|
|
71
|
+
for (const model of baseModels) {
|
|
72
|
+
modelMap.set(model.id, model);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const [id, patchEntry] of Object.entries(patchData)) {
|
|
76
|
+
const existing = modelMap.get(id);
|
|
77
|
+
if (existing) {
|
|
78
|
+
modelMap.set(id, applyPatch(existing, patchEntry));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const model of customModels) {
|
|
83
|
+
const existing = modelMap.get(model.id);
|
|
84
|
+
const patchEntry = patchData[model.id];
|
|
85
|
+
if (existing && patchEntry) {
|
|
86
|
+
modelMap.set(model.id, applyPatch(model, patchEntry));
|
|
87
|
+
} else if (existing) {
|
|
88
|
+
modelMap.set(model.id, model);
|
|
89
|
+
} else if (patchEntry) {
|
|
90
|
+
modelMap.set(model.id, applyPatch(model, patchEntry));
|
|
91
|
+
} else {
|
|
92
|
+
modelMap.set(model.id, model);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return Array.from(modelMap.values());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── Model transformation ─────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
function buildDisplayName(apiModel) {
|
|
102
|
+
const id = apiModel.id || '';
|
|
103
|
+
const parts = id.split('/');
|
|
104
|
+
const rawName = parts.length > 1 ? parts.slice(1).join('/') : id;
|
|
105
|
+
return rawName
|
|
106
|
+
.replace(/-/g, ' ')
|
|
107
|
+
.replace(/\b\w/g, c => c.toUpperCase());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function transformModel(apiModel, existingModelsMap) {
|
|
111
|
+
const modelId = apiModel.id;
|
|
112
|
+
|
|
113
|
+
// Preserve existing curated data (reasoning, vision, compat, context, etc.)
|
|
114
|
+
if (existingModelsMap[modelId]) {
|
|
115
|
+
const existing = { ...existingModelsMap[modelId] };
|
|
116
|
+
existing._meta = { isFree: true };
|
|
117
|
+
return existing;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// New model — build from API data + sensible defaults
|
|
121
|
+
return {
|
|
122
|
+
id: modelId,
|
|
123
|
+
name: buildDisplayName(apiModel),
|
|
124
|
+
reasoning: false,
|
|
125
|
+
input: ['text'],
|
|
126
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
127
|
+
contextWindow: 131072,
|
|
128
|
+
maxTokens: 131072,
|
|
129
|
+
_meta: { isFree: true },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─── File I/O ─────────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
function loadJson(filePath) {
|
|
136
|
+
try {
|
|
137
|
+
const data = fs.readFileSync(filePath, 'utf8');
|
|
138
|
+
return JSON.parse(data);
|
|
139
|
+
} catch {
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─── README generation ────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
function formatCost(cost, isFree) {
|
|
147
|
+
if (isFree) return '**Free**';
|
|
148
|
+
if (cost === 0) return '-';
|
|
149
|
+
return `$${cost.toFixed(2)}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function formatContextWindow(n) {
|
|
153
|
+
if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`;
|
|
154
|
+
if (n >= 1000) return `${Math.round(n / 1000)}K`;
|
|
155
|
+
return n.toString();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function generateReadmeTable(models) {
|
|
159
|
+
const lines = [
|
|
160
|
+
'| Model | Context | Vision | Reasoning | Input $/M | Output $/M |',
|
|
161
|
+
'|-------|---------|--------|-----------|-----------|------------|',
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
for (const model of models) {
|
|
165
|
+
const name = model.name;
|
|
166
|
+
const context = formatContextWindow(model.contextWindow);
|
|
167
|
+
const vision = model.input.includes('image') ? '✅' : '❌';
|
|
168
|
+
const reasoning = model.reasoning ? '✅' : '❌';
|
|
169
|
+
const isFree = model._meta?.isFree ?? (model.cost.input === 0 && model.cost.output === 0);
|
|
170
|
+
const inputCost = formatCost(model.cost.input, isFree);
|
|
171
|
+
const outputCost = formatCost(model.cost.output, isFree);
|
|
172
|
+
|
|
173
|
+
lines.push(`| ${name} | ${context} | ${vision} | ${reasoning} | ${inputCost} | ${outputCost} |`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return lines.join('\n');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function updateReadme(models) {
|
|
180
|
+
let readme = fs.readFileSync(README_PATH, 'utf8');
|
|
181
|
+
const newTable = generateReadmeTable(models);
|
|
182
|
+
|
|
183
|
+
const tableRegex = /(## Available Models\n\n)\| Model \| Context \| Vision \| Reasoning \| Input \$\/M \| Output \$\/M \|\n\|[-| ]+\|(\n\|[^\n]+\|)*\n*/;
|
|
184
|
+
|
|
185
|
+
if (tableRegex.test(readme)) {
|
|
186
|
+
readme = readme.replace(tableRegex, (match, header) => `${header}${newTable}\n\n`);
|
|
187
|
+
fs.writeFileSync(README_PATH, readme);
|
|
188
|
+
console.log('✓ Updated README.md');
|
|
189
|
+
} else {
|
|
190
|
+
console.warn('⚠ Could not find model table in "## Available Models" section');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function cleanModelForJson(model) {
|
|
195
|
+
const { _meta, ...cleanModel } = model;
|
|
196
|
+
return cleanModel;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ─── Main ──────────────────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
async function main() {
|
|
202
|
+
console.log(`Fetching models from ${MODELS_API_URL}...`);
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const apiKey = process.env.TENSORIX_API_KEY;
|
|
206
|
+
const headers = {};
|
|
207
|
+
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
208
|
+
|
|
209
|
+
const response = await fetch(MODELS_API_URL, { headers });
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const apiResponse = await response.json();
|
|
215
|
+
const apiModels = apiResponse.data || apiResponse;
|
|
216
|
+
|
|
217
|
+
if (!Array.isArray(apiModels)) {
|
|
218
|
+
throw new Error('API response does not contain an array of models');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log(`✓ Fetched ${apiModels.length} models from API`);
|
|
222
|
+
|
|
223
|
+
// Filter out non-chat models
|
|
224
|
+
const chatModels = apiModels.filter(m => !MODELS_TO_SKIP.has(m.id));
|
|
225
|
+
console.log(`✓ Filtered to ${chatModels.length} chat models (skipped ${apiModels.length - chatModels.length} non-chat)`);
|
|
226
|
+
|
|
227
|
+
// Load existing models.json — source of truth for curated specs
|
|
228
|
+
let existingModels = [];
|
|
229
|
+
try {
|
|
230
|
+
existingModels = JSON.parse(fs.readFileSync(MODELS_JSON_PATH, 'utf8'));
|
|
231
|
+
} catch {
|
|
232
|
+
// File might not exist or be invalid
|
|
233
|
+
}
|
|
234
|
+
const existingModelsMap = {};
|
|
235
|
+
for (const m of existingModels) {
|
|
236
|
+
existingModelsMap[m.id] = m;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Transform models from API, preserving existing curated data
|
|
240
|
+
let apiTransformed = chatModels.map(m => transformModel(m, existingModelsMap));
|
|
241
|
+
|
|
242
|
+
// Sort by id
|
|
243
|
+
apiTransformed.sort((a, b) => a.id.localeCompare(b.id));
|
|
244
|
+
|
|
245
|
+
// Update models.json — API-derived model list with curated specs preserved
|
|
246
|
+
const cleanModels = apiTransformed.map(cleanModelForJson);
|
|
247
|
+
fs.writeFileSync(MODELS_JSON_PATH, JSON.stringify(cleanModels, null, 2) + '\n');
|
|
248
|
+
console.log('✓ Updated models.json (API model list with curated specs)');
|
|
249
|
+
|
|
250
|
+
// Log new models not yet in patch.json
|
|
251
|
+
const patch = loadJson(PATCH_JSON_PATH);
|
|
252
|
+
for (const m of apiTransformed) {
|
|
253
|
+
if (!patch[m.id] && !existingModelsMap[m.id]) {
|
|
254
|
+
console.log(` 🆕 New model: ${m.id} (${m.name}) — add to patch.json for reasoning/vision/specs`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Build full model list for README: base → patch → custom
|
|
259
|
+
const customModels = loadJson(CUSTOM_MODELS_JSON_PATH);
|
|
260
|
+
const readmeModels = buildModels(
|
|
261
|
+
apiTransformed,
|
|
262
|
+
Array.isArray(customModels) ? customModels : [],
|
|
263
|
+
patch
|
|
264
|
+
);
|
|
265
|
+
readmeModels.sort((a, b) => a.name.localeCompare(b.name));
|
|
266
|
+
console.log('✓ Built model list (base → patch → custom) for README');
|
|
267
|
+
|
|
268
|
+
// Update README.md with patched data
|
|
269
|
+
updateReadme(readmeModels);
|
|
270
|
+
|
|
271
|
+
// Summary
|
|
272
|
+
console.log('\n--- Summary ---');
|
|
273
|
+
console.log(`Total models: ${readmeModels.length}`);
|
|
274
|
+
console.log(`Reasoning models (patched): ${readmeModels.filter(m => m.reasoning).length}`);
|
|
275
|
+
console.log(`Vision models: ${readmeModels.filter(m => m.input.includes('image')).length}`);
|
|
276
|
+
|
|
277
|
+
const newIds = new Set(apiTransformed.map(m => m.id));
|
|
278
|
+
const oldIds = new Set(existingModels.map(m => m.id));
|
|
279
|
+
|
|
280
|
+
const added = [...newIds].filter(id => !oldIds.has(id));
|
|
281
|
+
const removed = [...oldIds].filter(id => !newIds.has(id));
|
|
282
|
+
|
|
283
|
+
if (added.length > 0) {
|
|
284
|
+
console.log(`\nNew models: ${added.join(', ')}`);
|
|
285
|
+
}
|
|
286
|
+
if (removed.length > 0) {
|
|
287
|
+
console.log(`\nRemoved models: ${removed.join(', ')}`);
|
|
288
|
+
}
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.error('Error:', error.message);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
main();
|