pi-free 1.0.8 → 2.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/CHANGELOG.md +107 -1
- package/README.md +95 -46
- package/config.ts +165 -120
- package/constants.ts +22 -61
- package/index.ts +186 -0
- package/lib/json-persistence.ts +11 -10
- package/lib/logger.ts +2 -2
- package/lib/model-enhancer.ts +20 -20
- package/lib/open-browser.ts +41 -0
- package/lib/provider-cache.ts +106 -0
- package/lib/registry.ts +144 -0
- package/package.json +67 -82
- package/provider-factory.ts +25 -41
- package/provider-failover/benchmark-lookup.ts +247 -0
- package/provider-failover/benchmarks-chunk-0.ts +2010 -0
- package/provider-failover/benchmarks-chunk-1.ts +1988 -0
- package/provider-failover/benchmarks-chunk-2.ts +2010 -0
- package/provider-failover/benchmarks-chunk-3.ts +2010 -0
- package/provider-failover/benchmarks-chunk-4.ts +1969 -0
- package/provider-failover/hardcoded-benchmarks.ts +22 -10025
- package/provider-helper.ts +38 -37
- package/providers/{cline-auth.ts → cline/cline-auth.ts} +2 -2
- package/providers/cline/cline-models.ts +128 -0
- package/providers/{cline.ts → cline/cline.ts} +300 -257
- package/providers/cloudflare/cloudflare.ts +368 -0
- package/providers/dynamic-built-in/index.ts +513 -0
- package/providers/{kilo-auth.ts → kilo/kilo-auth.ts} +3 -20
- package/providers/{kilo-models.ts → kilo/kilo-models.ts} +2 -2
- package/providers/kilo/kilo.ts +235 -0
- package/providers/{modal.ts → modal/modal.ts} +4 -3
- package/providers/{nvidia.ts → nvidia/nvidia.ts} +152 -113
- package/providers/ollama/ollama.ts +172 -0
- package/providers/opencode-session.ts +34 -34
- package/providers/{qwen-auth.ts → qwen/qwen-auth.ts} +24 -40
- package/providers/{qwen-models.ts → qwen/qwen-models.ts} +101 -95
- package/providers/qwen/qwen.ts +202 -0
- package/provider-failover/auto-switch.ts +0 -350
- package/provider-failover/errors.ts +0 -275
- package/provider-failover/index.ts +0 -238
- package/providers/cline-models.ts +0 -77
- package/providers/factory.ts +0 -125
- package/providers/fireworks.ts +0 -49
- package/providers/go.ts +0 -216
- package/providers/kilo.ts +0 -146
- package/providers/mistral.ts +0 -144
- package/providers/ollama.ts +0 -113
- package/providers/openrouter.ts +0 -175
- package/providers/qwen.ts +0 -127
- package/providers/zen.ts +0 -371
- package/usage/commands.ts +0 -17
- package/usage/cumulative.ts +0 -193
- package/usage/formatters.ts +0 -115
- package/usage/index.ts +0 -46
- package/usage/limits.ts +0 -148
- package/usage/metrics.ts +0 -222
- package/usage/sessions.ts +0 -355
- package/usage/store.ts +0 -99
- package/usage/tracking.ts +0 -329
- package/usage/types.ts +0 -26
- package/usage/widget.ts +0 -90
- package/widget/data.ts +0 -113
- package/widget/format.ts +0 -26
- package/widget/render.ts +0 -117
package/package.json
CHANGED
|
@@ -1,82 +1,67 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pi-free",
|
|
3
|
-
"version": "
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "AIO
|
|
6
|
-
"keywords": [
|
|
7
|
-
"pi-package",
|
|
8
|
-
"pi-extension",
|
|
9
|
-
"free-models",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
},
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"provider-
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"@
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
"extensions": [
|
|
69
|
-
"./providers/kilo.ts",
|
|
70
|
-
"./providers/zen.ts",
|
|
71
|
-
"./providers/go.ts",
|
|
72
|
-
"./providers/openrouter.ts",
|
|
73
|
-
"./providers/nvidia.ts",
|
|
74
|
-
"./providers/cline.ts",
|
|
75
|
-
"./providers/fireworks.ts",
|
|
76
|
-
"./providers/mistral.ts",
|
|
77
|
-
"./providers/ollama.ts",
|
|
78
|
-
"./providers/qwen.ts",
|
|
79
|
-
"./providers/modal.ts"
|
|
80
|
-
]
|
|
81
|
-
}
|
|
82
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-free",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "AIO free models for PI: Kilo, Cline, Nvidia, Ollama Cloud and others",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"free-models",
|
|
10
|
+
"model-filter",
|
|
11
|
+
"nvidia-nim",
|
|
12
|
+
"kilo",
|
|
13
|
+
"cline",
|
|
14
|
+
"qwen",
|
|
15
|
+
"qwen-oauth",
|
|
16
|
+
"modal"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Apostolos Mantzaris",
|
|
20
|
+
"homepage": "https://github.com/apmantza/pi-free#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/apmantza/pi-free/issues"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/apmantza/pi-free.git"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.0.0"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"index.ts",
|
|
33
|
+
"providers/**/*.ts",
|
|
34
|
+
"lib/**/*.ts",
|
|
35
|
+
"provider-failover/**/*.ts",
|
|
36
|
+
"config.ts",
|
|
37
|
+
"constants.ts",
|
|
38
|
+
"provider-factory.ts",
|
|
39
|
+
"provider-helper.ts",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE",
|
|
42
|
+
"CHANGELOG.md",
|
|
43
|
+
"scripts/check-extensions.mjs"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"check": "node scripts/check-extensions.mjs",
|
|
47
|
+
"test": "vitest",
|
|
48
|
+
"test:ui": "vitest --ui",
|
|
49
|
+
"test:run": "vitest run"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@mariozechner/pi-ai": "*",
|
|
53
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
54
|
+
"@mariozechner/pi-tui": "*"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@vitest/ui": "^1.0.0",
|
|
58
|
+
"tsx": "^4.0.0",
|
|
59
|
+
"typescript": "^6.0.2",
|
|
60
|
+
"vitest": "^1.0.0"
|
|
61
|
+
},
|
|
62
|
+
"pi": {
|
|
63
|
+
"extensions": [
|
|
64
|
+
"./index.ts"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|
package/provider-factory.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Provider Factory
|
|
3
3
|
*
|
|
4
4
|
* Extracts the common boilerplate pattern repeated across providers:
|
|
5
|
-
* - API key check
|
|
5
|
+
* - API key check
|
|
6
6
|
* - SHOW_PAID flag check
|
|
7
7
|
* - Model fetching with error handling
|
|
8
8
|
* - Provider registration
|
|
@@ -23,22 +23,16 @@ import type {
|
|
|
23
23
|
ProviderModelConfig,
|
|
24
24
|
} from "@mariozechner/pi-coding-agent";
|
|
25
25
|
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
NVIDIA_API_KEY,
|
|
31
|
-
NVIDIA_SHOW_PAID,
|
|
32
|
-
OLLAMA_API_KEY,
|
|
33
|
-
OLLAMA_SHOW_PAID,
|
|
34
|
-
OPENCODE_API_KEY,
|
|
35
|
-
ZEN_SHOW_PAID,
|
|
36
|
-
MODAL_API_KEY,
|
|
26
|
+
getModalApiKey,
|
|
27
|
+
getNvidiaApiKey,
|
|
28
|
+
getNvidiaShowPaid,
|
|
29
|
+
getOpencodeApiKey,
|
|
37
30
|
} from "./config.ts";
|
|
38
31
|
import { createLogger } from "./lib/logger.ts";
|
|
39
32
|
import { logWarning } from "./lib/util.ts";
|
|
40
33
|
import {
|
|
41
34
|
createReRegister,
|
|
35
|
+
enhanceWithCI,
|
|
42
36
|
type StoredModels,
|
|
43
37
|
setupProvider,
|
|
44
38
|
} from "./provider-helper.ts";
|
|
@@ -50,7 +44,7 @@ const _logger = createLogger("provider-factory");
|
|
|
50
44
|
// =============================================================================
|
|
51
45
|
|
|
52
46
|
export interface ProviderDefinition {
|
|
53
|
-
/** Provider identifier (e.g., "nvidia", "
|
|
47
|
+
/** Provider identifier (e.g., "nvidia", "modal") */
|
|
54
48
|
providerId: string;
|
|
55
49
|
/** Base URL for the API */
|
|
56
50
|
baseUrl: string;
|
|
@@ -66,6 +60,8 @@ export interface ProviderDefinition {
|
|
|
66
60
|
tosUrl?: string;
|
|
67
61
|
/** Whether this provider has a free tier (free + paid models). Default: false */
|
|
68
62
|
hasFreeTier?: boolean;
|
|
63
|
+
/** Whether to skip creating a toggle command (e.g., for single-model providers). Default: false */
|
|
64
|
+
skipToggle?: boolean;
|
|
69
65
|
/** Additional headers to include in requests */
|
|
70
66
|
extraHeaders?: Record<string, string>;
|
|
71
67
|
/** Optional hook to modify request payload before sending */
|
|
@@ -80,22 +76,14 @@ export interface ProviderDefinition {
|
|
|
80
76
|
// Config value getters (dynamic lookup)
|
|
81
77
|
// =============================================================================
|
|
82
78
|
|
|
83
|
-
// Map config key names to their values
|
|
84
79
|
const API_KEY_GETTERS: Record<string, () => string | undefined> = {
|
|
85
|
-
nvidia_api_key:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
mistral_api_key: () => MISTRAL_API_KEY,
|
|
89
|
-
opencode_api_key: () => OPENCODE_API_KEY,
|
|
90
|
-
modal_api_key: () => MODAL_API_KEY,
|
|
80
|
+
nvidia_api_key: getNvidiaApiKey,
|
|
81
|
+
opencode_api_key: getOpencodeApiKey,
|
|
82
|
+
modal_api_key: getModalApiKey,
|
|
91
83
|
};
|
|
92
84
|
|
|
93
85
|
const SHOW_PAID_GETTERS: Record<string, () => boolean> = {
|
|
94
|
-
NVIDIA_SHOW_PAID:
|
|
95
|
-
FIREWORKS_SHOW_PAID: () => FIREWORKS_SHOW_PAID,
|
|
96
|
-
OLLAMA_SHOW_PAID: () => OLLAMA_SHOW_PAID,
|
|
97
|
-
MISTRAL_SHOW_PAID: () => MISTRAL_SHOW_PAID,
|
|
98
|
-
ZEN_SHOW_PAID: () => ZEN_SHOW_PAID,
|
|
86
|
+
NVIDIA_SHOW_PAID: getNvidiaShowPaid,
|
|
99
87
|
};
|
|
100
88
|
|
|
101
89
|
// =============================================================================
|
|
@@ -106,7 +94,7 @@ const SHOW_PAID_GETTERS: Record<string, () => boolean> = {
|
|
|
106
94
|
* Create a provider with minimal boilerplate.
|
|
107
95
|
*
|
|
108
96
|
* Handles:
|
|
109
|
-
* - API key check
|
|
97
|
+
* - API key check
|
|
110
98
|
* - SHOW_PAID flag check (if applicable)
|
|
111
99
|
* - Model fetching with error handling
|
|
112
100
|
* - Provider registration with OpenAI-compatible API
|
|
@@ -126,12 +114,7 @@ export async function createProvider(
|
|
|
126
114
|
}
|
|
127
115
|
const apiKey = getApiKey();
|
|
128
116
|
|
|
129
|
-
// 2.
|
|
130
|
-
if (apiKey) {
|
|
131
|
-
process.env[def.apiKeyEnvVar] = apiKey;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 3. Check key exists
|
|
117
|
+
// 2. Check key exists
|
|
135
118
|
if (!apiKey) {
|
|
136
119
|
_logger.warn(
|
|
137
120
|
`No API key found — set ${def.apiKeyEnvVar} or add ${def.apiKeyConfigKey} to ~/.pi/free.json`,
|
|
@@ -139,7 +122,7 @@ export async function createProvider(
|
|
|
139
122
|
return;
|
|
140
123
|
}
|
|
141
124
|
|
|
142
|
-
//
|
|
125
|
+
// 3. Check paid flag (if applicable)
|
|
143
126
|
if (def.showPaidFlag) {
|
|
144
127
|
const getShowPaid = SHOW_PAID_GETTERS[def.showPaidFlag];
|
|
145
128
|
if (getShowPaid && !getShowPaid()) {
|
|
@@ -150,7 +133,7 @@ export async function createProvider(
|
|
|
150
133
|
}
|
|
151
134
|
}
|
|
152
135
|
|
|
153
|
-
//
|
|
136
|
+
// 4. Fetch models
|
|
154
137
|
let models: ProviderModelConfig[] = [];
|
|
155
138
|
try {
|
|
156
139
|
models = await def.fetchModels();
|
|
@@ -164,7 +147,7 @@ export async function createProvider(
|
|
|
164
147
|
return;
|
|
165
148
|
}
|
|
166
149
|
|
|
167
|
-
//
|
|
150
|
+
// 5. Build storage (free/all or single set)
|
|
168
151
|
const stored: StoredModels = def.hasFreeTier
|
|
169
152
|
? {
|
|
170
153
|
free: models.filter((m) => (m.cost?.input ?? 0) === 0),
|
|
@@ -172,23 +155,23 @@ export async function createProvider(
|
|
|
172
155
|
}
|
|
173
156
|
: { free: models, all: models };
|
|
174
157
|
|
|
175
|
-
//
|
|
158
|
+
// 6. Register provider (pass literal key so we don't mutate process.env)
|
|
176
159
|
pi.registerProvider(def.providerId, {
|
|
177
160
|
baseUrl: def.baseUrl,
|
|
178
|
-
apiKey
|
|
161
|
+
apiKey,
|
|
179
162
|
api: "openai-completions" as const,
|
|
180
163
|
headers: {
|
|
181
164
|
"User-Agent": "pi-free-providers",
|
|
182
165
|
...def.extraHeaders,
|
|
183
166
|
},
|
|
184
|
-
models,
|
|
167
|
+
models: enhanceWithCI(models),
|
|
185
168
|
});
|
|
186
169
|
|
|
187
|
-
//
|
|
170
|
+
// 7. Setup boilerplate
|
|
188
171
|
const config = {
|
|
189
172
|
providerId: def.providerId,
|
|
190
173
|
baseUrl: def.baseUrl,
|
|
191
|
-
apiKey
|
|
174
|
+
apiKey,
|
|
192
175
|
};
|
|
193
176
|
|
|
194
177
|
const reRegister = createReRegister(pi, config);
|
|
@@ -204,11 +187,12 @@ export async function createProvider(
|
|
|
204
187
|
stored.all = m;
|
|
205
188
|
reRegister(m);
|
|
206
189
|
},
|
|
190
|
+
skipToggle: def.skipToggle,
|
|
207
191
|
},
|
|
208
192
|
stored,
|
|
209
193
|
);
|
|
210
194
|
|
|
211
|
-
//
|
|
195
|
+
// 8. Optional: before_provider_request hook
|
|
212
196
|
if (def.beforeProviderRequest) {
|
|
213
197
|
const hook = def.beforeProviderRequest;
|
|
214
198
|
(pi.on as (event: string, handler: (e: unknown) => unknown) => void)(
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Benchmark lookup logic — extracted from hardcoded-benchmarks.ts
|
|
3
|
+
* for maintainability (the data file is ~10k lines of JSON-like entries).
|
|
4
|
+
*
|
|
5
|
+
* This module re-exports everything consumers currently import from
|
|
6
|
+
* hardcoded-benchmarks, so you can switch imports to this file without
|
|
7
|
+
* breaking anything.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
HARDCODED_BENCHMARKS,
|
|
12
|
+
type HardcodedBenchmark,
|
|
13
|
+
} from "./hardcoded-benchmarks.ts";
|
|
14
|
+
|
|
15
|
+
// Re-export the type and data so callers can migrate imports here
|
|
16
|
+
export { HARDCODED_BENCHMARKS, type HardcodedBenchmark };
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Prefix fallback helpers
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Segments that indicate a variant of the same base model
|
|
24
|
+
* (effort level, reasoning mode, date, preview) — NOT a fundamentally different model.
|
|
25
|
+
* Used to filter prefix matches so we don't cross model boundaries
|
|
26
|
+
* (e.g. gpt-4o → gpt-4o-mini is wrong, but gpt-4o → gpt-4o-aug-24 is fine).
|
|
27
|
+
*/
|
|
28
|
+
const VARIANT_QUALIFIER_SEGMENTS = new Set([
|
|
29
|
+
"reasoning",
|
|
30
|
+
"non-reasoning",
|
|
31
|
+
"high",
|
|
32
|
+
"low",
|
|
33
|
+
"medium",
|
|
34
|
+
"xhigh",
|
|
35
|
+
"preview",
|
|
36
|
+
"adaptive",
|
|
37
|
+
"fast",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if a segment is a variant qualifier rather than a different model identifier.
|
|
42
|
+
* Accepts effort levels, reasoning modes, date codes, size specifiers, and version numbers.
|
|
43
|
+
*/
|
|
44
|
+
function isVariantQualifier(segment: string): boolean {
|
|
45
|
+
if (VARIANT_QUALIFIER_SEGMENTS.has(segment)) return true;
|
|
46
|
+
// Date codes like "0528", "20250514"
|
|
47
|
+
if (/^\d{4,8}$/.test(segment)) return true;
|
|
48
|
+
// Month names (from date suffixes like "may-25", "mar-24")
|
|
49
|
+
if (/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)$/.test(segment))
|
|
50
|
+
return true;
|
|
51
|
+
// Size specifiers like "70b", "8b", "a35b", "a3b" (MoE notation)
|
|
52
|
+
if (/^a?\d+(\.\d+)?b$/i.test(segment)) return true;
|
|
53
|
+
// Version numbers like "v3.2", "v2.5", "v1"
|
|
54
|
+
if (/^v\d+(\.\d+)?$/.test(segment)) return true;
|
|
55
|
+
// Two-digit year like "25", "24"
|
|
56
|
+
if (/^\d{2}$/.test(segment)) return true;
|
|
57
|
+
// Special variant suffixes
|
|
58
|
+
if (segment === "speciale" || segment === "chatgpt" || segment === "latest")
|
|
59
|
+
return true;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Normalize model ID by reordering size tokens to match AA convention.
|
|
65
|
+
* Converts "70b-instruct" → "instruct-70b", "405b-chat" → "chat-405b".
|
|
66
|
+
* AA uses instruct-70b order while providers often use 70b-instruct.
|
|
67
|
+
*/
|
|
68
|
+
function normalizeSizeTokenOrder(id: string): string {
|
|
69
|
+
return id.replace(/(\d+(?:\.\d+)?b)-(instruct|chat)/gi, "$2-$1");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Extract the base model ID from a provider model ID.
|
|
74
|
+
* Strips provider prefix ("openai/"), :free suffix, date suffixes, and version suffixes.
|
|
75
|
+
*/
|
|
76
|
+
function extractBaseModelId(modelId: string): string {
|
|
77
|
+
return modelId
|
|
78
|
+
.toLowerCase()
|
|
79
|
+
.replace(/^[^/]+\//, "") // Strip provider prefix like "openai/"
|
|
80
|
+
.replace(/:free$/, "") // Strip :free suffix
|
|
81
|
+
.replace(/-\d{8}$/, "") // Strip date suffixes like -20250514
|
|
82
|
+
.replace(/-v\d+(\.\d+)?$/, "") // Strip version suffixes like -v1.1
|
|
83
|
+
.replace(/-\d{3,}$/, "") // Strip numeric suffixes like -001, -2603
|
|
84
|
+
.replace(/-it$/, "") // Strip -it suffix (Gemma convention for "instruct")
|
|
85
|
+
.replace(/-fp\d+$/, "") // Strip -fp8, -fp16 suffixes
|
|
86
|
+
.replace(/-bf\d+$/, "") // Strip -bf16 suffixes
|
|
87
|
+
.trim();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Find the best benchmark variant by prefix matching.
|
|
92
|
+
* Given a base model ID, finds all benchmark keys that are variants of it
|
|
93
|
+
* (same base model with effort/reasoning/date qualifiers) and returns the
|
|
94
|
+
* variant with the highest codingIndex.
|
|
95
|
+
*/
|
|
96
|
+
function findBestVariantByPrefix(baseId: string): HardcodedBenchmark | null {
|
|
97
|
+
const prefixKey = baseId + "-";
|
|
98
|
+
const candidates: { key: string; data: HardcodedBenchmark }[] = [];
|
|
99
|
+
|
|
100
|
+
for (const [key, data] of Object.entries(HARDCODED_BENCHMARKS) as [
|
|
101
|
+
string,
|
|
102
|
+
HardcodedBenchmark,
|
|
103
|
+
][]) {
|
|
104
|
+
// Exact match
|
|
105
|
+
if (key === baseId) {
|
|
106
|
+
if (data.codingIndex !== undefined) return data;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Prefix match: key starts with baseId + "-"
|
|
111
|
+
if (key.startsWith(prefixKey)) {
|
|
112
|
+
// Check that the first segment after the prefix is a qualifier
|
|
113
|
+
// (prevents gpt-4o → gpt-4o-mini cross-model matches)
|
|
114
|
+
const remainder = key.slice(prefixKey.length);
|
|
115
|
+
const firstSegment = remainder.split("-")[0]!;
|
|
116
|
+
if (isVariantQualifier(firstSegment)) {
|
|
117
|
+
candidates.push({ key, data });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (candidates.length === 0) return null;
|
|
123
|
+
|
|
124
|
+
// Pick the candidate with the highest codingIndex
|
|
125
|
+
// If tied or no CI, use normalizedScore as tiebreaker
|
|
126
|
+
candidates.sort((a, b) => {
|
|
127
|
+
const ciA = a.data.codingIndex ?? -1;
|
|
128
|
+
const ciB = b.data.codingIndex ?? -1;
|
|
129
|
+
if (ciB !== ciA) return ciB - ciA;
|
|
130
|
+
return (b.data.normalizedScore ?? 0) - (a.data.normalizedScore ?? 0);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Only return if the best candidate has a codingIndex
|
|
134
|
+
if (candidates[0]!.data.codingIndex !== undefined) {
|
|
135
|
+
return candidates[0]!.data;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// =============================================================================
|
|
142
|
+
// Main lookup
|
|
143
|
+
// =============================================================================
|
|
144
|
+
|
|
145
|
+
export function findHardcodedBenchmark(
|
|
146
|
+
modelName: string,
|
|
147
|
+
modelId: string,
|
|
148
|
+
): HardcodedBenchmark | null {
|
|
149
|
+
const search = `${modelName} ${modelId}`.toLowerCase();
|
|
150
|
+
|
|
151
|
+
// 1. Direct lookup — check if any benchmark key is a substring of the search
|
|
152
|
+
for (const [key, data] of Object.entries(HARDCODED_BENCHMARKS) as [
|
|
153
|
+
string,
|
|
154
|
+
HardcodedBenchmark,
|
|
155
|
+
][]) {
|
|
156
|
+
if (search.includes(key.toLowerCase())) {
|
|
157
|
+
return data;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 2. Variant matching — aliases for models with different naming conventions
|
|
162
|
+
const variants: Record<string, string[]> = {
|
|
163
|
+
"gpt-4o-aug-24": ["gpt-4o", "gpt-4-o"],
|
|
164
|
+
"gpt-4": ["gpt-4", "gpt4"],
|
|
165
|
+
"claude-3.5-sonnet-oct-24": [
|
|
166
|
+
"claude-3.5-sonnet",
|
|
167
|
+
"claude-3-5-sonnet",
|
|
168
|
+
"sonnet-3.5",
|
|
169
|
+
],
|
|
170
|
+
"claude-3-opus": ["claude-3-opus", "opus-3"],
|
|
171
|
+
"llama-3.1-instruct-405b": [
|
|
172
|
+
"llama-3.1-405b",
|
|
173
|
+
"llama3.1-405b",
|
|
174
|
+
"llama-405b",
|
|
175
|
+
],
|
|
176
|
+
"llama-3.1-instruct-70b": ["llama-3.1-70b", "llama3.1-70b", "llama-70b"],
|
|
177
|
+
"gemini-1.5-pro": ["gemini-1.5-pro", "gemini1.5-pro", "gemini-pro-1.5"],
|
|
178
|
+
"qwen2.5-instruct-72b": ["qwen2.5-72b", "qwen-2.5-72b"],
|
|
179
|
+
"deepseek-v3.2-non-reasoning": [
|
|
180
|
+
"deepseek-v3",
|
|
181
|
+
"deepseekv3",
|
|
182
|
+
"deepseek-chat",
|
|
183
|
+
],
|
|
184
|
+
"mimo-v2-pro": ["mimo-v2-pro", "mimo-v2-pro-free", "mimo-pro"],
|
|
185
|
+
"mimo-v2-omni": ["mimo-v2-omni", "mimo-v2-omni-free", "mimo-omni"],
|
|
186
|
+
"mimo-v2-flash": ["mimo-v2-flash", "mimo-v2-flash-free", "mimo-flash"],
|
|
187
|
+
"big-pickle": ["big-pickle", "bigpickle"],
|
|
188
|
+
"minimax-m2.5": ["minimax-m2.5", "minimax-m2.5-free", "minimax-m25"],
|
|
189
|
+
"nvidia-nemotron-3-super-120b-a12b-reasoning": [
|
|
190
|
+
"nemotron-3-super",
|
|
191
|
+
"nemotron-3-super-free",
|
|
192
|
+
"nemotron-super",
|
|
193
|
+
"nemotron-3",
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
for (const [canonical, names] of Object.entries(variants)) {
|
|
198
|
+
if (names.some((n) => search.includes(n.toLowerCase()))) {
|
|
199
|
+
return HARDCODED_BENCHMARKS[canonical] || null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 3. Prefix fallback — extract base model ID and find best variant
|
|
204
|
+
// Handles cases where benchmark keys have variant suffixes
|
|
205
|
+
// (reasoning/non-reasoning, effort levels, dates) that the model ID lacks
|
|
206
|
+
const baseId = extractBaseModelId(modelId);
|
|
207
|
+
if (baseId) {
|
|
208
|
+
let best = findBestVariantByPrefix(baseId);
|
|
209
|
+
if (best) return best;
|
|
210
|
+
|
|
211
|
+
// 3b. Try with word-order normalization
|
|
212
|
+
// (e.g., llama-3.3-70b-instruct → llama-3.3-instruct-70b)
|
|
213
|
+
const normalizedId = normalizeSizeTokenOrder(baseId);
|
|
214
|
+
if (normalizedId !== baseId) {
|
|
215
|
+
best = findBestVariantByPrefix(normalizedId);
|
|
216
|
+
if (best) return best;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get score from hardcoded data
|
|
225
|
+
*/
|
|
226
|
+
export function getHardcodedScore(
|
|
227
|
+
modelName: string,
|
|
228
|
+
modelId: string,
|
|
229
|
+
): number | null {
|
|
230
|
+
const benchmark = findHardcodedBenchmark(modelName, modelId);
|
|
231
|
+
return benchmark?.normalizedScore ?? null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Enhance model name with Coding Index score
|
|
236
|
+
* Returns model name with CI score appended if available
|
|
237
|
+
*/
|
|
238
|
+
export function enhanceModelNameWithCodingIndex(
|
|
239
|
+
modelName: string,
|
|
240
|
+
modelId: string,
|
|
241
|
+
): string {
|
|
242
|
+
const benchmark = findHardcodedBenchmark(modelName, modelId);
|
|
243
|
+
if (benchmark?.codingIndex !== undefined) {
|
|
244
|
+
return `${modelName} [CI: ${benchmark.codingIndex.toFixed(1)}]`;
|
|
245
|
+
}
|
|
246
|
+
return modelName;
|
|
247
|
+
}
|