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/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();