free-coding-models 0.1.79 β 0.1.81
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/bin/free-coding-models.js +204 -162
- package/lib/utils.js +19 -0
- package/package.json +1 -1
|
@@ -95,7 +95,7 @@ import { createServer as createHttpServer } from 'http'
|
|
|
95
95
|
import { request as httpsRequest } from 'https'
|
|
96
96
|
import { MODELS, sources } from '../sources.js'
|
|
97
97
|
import { patchOpenClawModelsJson } from '../patch-openclaw-models.js'
|
|
98
|
-
import { getAvg, getVerdict, getUptime, getP95, getJitter, getStabilityScore, sortResults, filterByTier, findBestModel, parseArgs, TIER_ORDER, VERDICT_ORDER, TIER_LETTER_MAP, scoreModelForTask, getTopRecommendations, TASK_TYPES, PRIORITY_TYPES, CONTEXT_BUDGETS } from '../lib/utils.js'
|
|
98
|
+
import { getAvg, getVerdict, getUptime, getP95, getJitter, getStabilityScore, sortResults, filterByTier, findBestModel, parseArgs, TIER_ORDER, VERDICT_ORDER, TIER_LETTER_MAP, scoreModelForTask, getTopRecommendations, TASK_TYPES, PRIORITY_TYPES, CONTEXT_BUDGETS, formatCtxWindow, labelFromId } from '../lib/utils.js'
|
|
99
99
|
import { loadConfig, saveConfig, getApiKey, isProviderEnabled, saveAsProfile, loadProfile, listProfiles, deleteProfile, getActiveProfileName, setActiveProfile, _emptyProfileSettings } from '../lib/config.js'
|
|
100
100
|
|
|
101
101
|
const require = createRequire(import.meta.url)
|
|
@@ -1384,7 +1384,10 @@ const OPENCODE_MODEL_MAP = {
|
|
|
1384
1384
|
}
|
|
1385
1385
|
|
|
1386
1386
|
function getOpenCodeModelId(providerKey, modelId) {
|
|
1387
|
-
// π
|
|
1387
|
+
// π Model IDs in sources.js include the provider prefix (e.g. "nvidia/llama-3.1-...")
|
|
1388
|
+
// π but OpenCode expects just the model part after provider/ since we build
|
|
1389
|
+
// π the full ref as `${providerKey}/${ocModelId}` in startOpenCode
|
|
1390
|
+
if (providerKey === 'nvidia') return modelId.replace(/^nvidia\//, '')
|
|
1388
1391
|
if (providerKey === 'zai') return modelId.replace(/^zai\//, '')
|
|
1389
1392
|
return OPENCODE_MODEL_MAP[providerKey]?.[modelId] || modelId
|
|
1390
1393
|
}
|
|
@@ -1622,22 +1625,6 @@ function saveOpenCodeConfig(config) {
|
|
|
1622
1625
|
writeFileSync(configPath, JSON.stringify(config, null, 2))
|
|
1623
1626
|
}
|
|
1624
1627
|
|
|
1625
|
-
// βββ Check NVIDIA NIM in OpenCode config βββββββββββββββββββββββββββββββββββββββ
|
|
1626
|
-
// π Checks if NVIDIA NIM provider is configured in OpenCode config file
|
|
1627
|
-
// π OpenCode uses 'provider' (singular) not 'providers' (plural)
|
|
1628
|
-
// π Returns true if found, false otherwise
|
|
1629
|
-
function checkNvidiaNimConfig() {
|
|
1630
|
-
const config = loadOpenCodeConfig()
|
|
1631
|
-
if (!config.provider) return false
|
|
1632
|
-
// π Check for nvidia/nim provider by key name or display name (case-insensitive)
|
|
1633
|
-
const providerKeys = Object.keys(config.provider)
|
|
1634
|
-
return providerKeys.some(key =>
|
|
1635
|
-
key === 'nvidia' || key === 'nim' ||
|
|
1636
|
-
config.provider[key]?.name?.toLowerCase().includes('nvidia') ||
|
|
1637
|
-
config.provider[key]?.name?.toLowerCase().includes('nim')
|
|
1638
|
-
)
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
1628
|
// βββ Shared OpenCode spawn helper ββββββββββββββββββββββββββββββββββββββββββββββ
|
|
1642
1629
|
// π Resolves the actual API key from config/env and passes it as an env var
|
|
1643
1630
|
// π to the child process so OpenCode's {env:GROQ_API_KEY} references work
|
|
@@ -1773,83 +1760,60 @@ async function startOpenCode(model, fcmConfig) {
|
|
|
1773
1760
|
|
|
1774
1761
|
if (providerKey === 'nvidia') {
|
|
1775
1762
|
// π NVIDIA NIM needs a custom provider block in OpenCode config (not built-in)
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
console.log(chalk.green(` π Setting ${chalk.bold(model.label)} as defaultβ¦`))
|
|
1780
|
-
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
1781
|
-
console.log()
|
|
1763
|
+
// π Auto-create it if missing β same pattern as all other providers
|
|
1764
|
+
const config = loadOpenCodeConfig()
|
|
1765
|
+
const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
|
|
1782
1766
|
|
|
1783
|
-
|
|
1784
|
-
|
|
1767
|
+
if (existsSync(getOpenCodeConfigPath())) {
|
|
1768
|
+
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
1769
|
+
console.log(chalk.dim(` Backup: ${backupPath}`))
|
|
1770
|
+
}
|
|
1785
1771
|
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1772
|
+
// π Ensure nvidia provider block exists β auto-create if missing
|
|
1773
|
+
if (!config.provider) config.provider = {}
|
|
1774
|
+
if (!config.provider.nvidia) {
|
|
1775
|
+
config.provider.nvidia = {
|
|
1776
|
+
npm: '@ai-sdk/openai-compatible',
|
|
1777
|
+
name: 'NVIDIA NIM',
|
|
1778
|
+
options: {
|
|
1779
|
+
baseURL: 'https://integrate.api.nvidia.com/v1',
|
|
1780
|
+
apiKey: '{env:NVIDIA_API_KEY}'
|
|
1781
|
+
},
|
|
1782
|
+
models: {}
|
|
1789
1783
|
}
|
|
1784
|
+
console.log(chalk.green(' + Auto-configured NVIDIA NIM provider in OpenCode'))
|
|
1785
|
+
}
|
|
1790
1786
|
|
|
1791
|
-
|
|
1787
|
+
console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default...`))
|
|
1788
|
+
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
1789
|
+
console.log()
|
|
1792
1790
|
|
|
1793
|
-
|
|
1794
|
-
if (config.provider?.nvidia) {
|
|
1795
|
-
if (!config.provider.nvidia.models) config.provider.nvidia.models = {}
|
|
1796
|
-
config.provider.nvidia.models[ocModelId] = { name: model.label }
|
|
1797
|
-
}
|
|
1791
|
+
config.model = modelRef
|
|
1798
1792
|
|
|
1799
|
-
|
|
1793
|
+
// π Register the model in the nvidia provider's models section
|
|
1794
|
+
if (!config.provider.nvidia.models) config.provider.nvidia.models = {}
|
|
1795
|
+
config.provider.nvidia.models[ocModelId] = { name: model.label }
|
|
1800
1796
|
|
|
1801
|
-
|
|
1802
|
-
console.log(chalk.dim(` π Config saved to: ${getOpenCodeConfigPath()}`))
|
|
1803
|
-
console.log(chalk.dim(` π Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
1804
|
-
console.log()
|
|
1797
|
+
saveOpenCodeConfig(config)
|
|
1805
1798
|
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
}
|
|
1811
|
-
console.log()
|
|
1812
|
-
console.log(chalk.dim(' Starting OpenCodeβ¦'))
|
|
1813
|
-
console.log()
|
|
1799
|
+
const savedConfig = loadOpenCodeConfig()
|
|
1800
|
+
console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
|
|
1801
|
+
console.log(chalk.dim(` Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
1802
|
+
console.log()
|
|
1814
1803
|
|
|
1815
|
-
|
|
1804
|
+
if (savedConfig.model === config.model) {
|
|
1805
|
+
console.log(chalk.green(` Default model set to: ${modelRef}`))
|
|
1816
1806
|
} else {
|
|
1817
|
-
|
|
1818
|
-
console.log(chalk.yellow(' β NVIDIA NIM not configured in OpenCode'))
|
|
1819
|
-
console.log()
|
|
1820
|
-
console.log(chalk.dim(' Starting OpenCode with installation promptβ¦'))
|
|
1821
|
-
console.log()
|
|
1822
|
-
|
|
1823
|
-
const configPath = getOpenCodeConfigPath()
|
|
1824
|
-
const installPrompt = `Please install NVIDIA NIM provider in OpenCode by adding this to ${configPath}:
|
|
1825
|
-
|
|
1826
|
-
{
|
|
1827
|
-
"provider": {
|
|
1828
|
-
"nvidia": {
|
|
1829
|
-
"npm": "@ai-sdk/openai-compatible",
|
|
1830
|
-
"name": "NVIDIA NIM",
|
|
1831
|
-
"options": {
|
|
1832
|
-
"baseURL": "https://integrate.api.nvidia.com/v1",
|
|
1833
|
-
"apiKey": "{env:NVIDIA_API_KEY}"
|
|
1834
|
-
}
|
|
1807
|
+
console.log(chalk.yellow(` Config might not have been saved correctly`))
|
|
1835
1808
|
}
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_key_here'}
|
|
1840
|
-
|
|
1841
|
-
After installation, you can use: opencode --model ${modelRef}`
|
|
1842
|
-
|
|
1843
|
-
console.log(chalk.cyan(installPrompt))
|
|
1844
|
-
console.log()
|
|
1845
|
-
console.log(chalk.dim(' Starting OpenCodeβ¦'))
|
|
1846
|
-
console.log()
|
|
1809
|
+
console.log()
|
|
1810
|
+
console.log(chalk.dim(' Starting OpenCode...'))
|
|
1811
|
+
console.log()
|
|
1847
1812
|
|
|
1848
|
-
|
|
1849
|
-
}
|
|
1813
|
+
await spawnOpenCode(['--model', modelRef], providerKey, fcmConfig)
|
|
1850
1814
|
} else {
|
|
1851
1815
|
if (providerKey === 'replicate') {
|
|
1852
|
-
console.log(chalk.yellow('
|
|
1816
|
+
console.log(chalk.yellow(' Replicate models are monitor-only for now in OpenCode mode.'))
|
|
1853
1817
|
console.log(chalk.dim(' Reason: Replicate uses /v1/predictions instead of OpenAI chat-completions.'))
|
|
1854
1818
|
console.log(chalk.dim(' You can still benchmark this model in the TUI and use other providers for OpenCode launch.'))
|
|
1855
1819
|
console.log()
|
|
@@ -1863,16 +1827,16 @@ After installation, you can use: opencode --model ${modelRef}`
|
|
|
1863
1827
|
if (providerKey === 'zai') {
|
|
1864
1828
|
const resolvedKey = getApiKey(fcmConfig, providerKey)
|
|
1865
1829
|
if (!resolvedKey) {
|
|
1866
|
-
console.log(chalk.yellow('
|
|
1830
|
+
console.log(chalk.yellow(' ZAI API key not found. Set ZAI_API_KEY environment variable.'))
|
|
1867
1831
|
console.log()
|
|
1868
1832
|
return
|
|
1869
1833
|
}
|
|
1870
1834
|
|
|
1871
1835
|
// π Start proxy FIRST to get the port for config
|
|
1872
1836
|
const { server: zaiProxyServer, port: zaiProxyPort } = await createZaiProxy(resolvedKey)
|
|
1873
|
-
console.log(chalk.dim(`
|
|
1837
|
+
console.log(chalk.dim(` ZAI proxy listening on port ${zaiProxyPort} (rewrites /v1/* -> ZAI API)`))
|
|
1874
1838
|
|
|
1875
|
-
console.log(chalk.green(`
|
|
1839
|
+
console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default...`))
|
|
1876
1840
|
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
1877
1841
|
console.log()
|
|
1878
1842
|
|
|
@@ -1881,7 +1845,7 @@ After installation, you can use: opencode --model ${modelRef}`
|
|
|
1881
1845
|
|
|
1882
1846
|
if (existsSync(getOpenCodeConfigPath())) {
|
|
1883
1847
|
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
1884
|
-
console.log(chalk.dim(`
|
|
1848
|
+
console.log(chalk.dim(` Backup: ${backupPath}`))
|
|
1885
1849
|
}
|
|
1886
1850
|
|
|
1887
1851
|
// π Register ZAI as an openai-compatible provider pointing to our localhost proxy
|
|
@@ -1902,17 +1866,17 @@ After installation, you can use: opencode --model ${modelRef}`
|
|
|
1902
1866
|
saveOpenCodeConfig(config)
|
|
1903
1867
|
|
|
1904
1868
|
const savedConfig = loadOpenCodeConfig()
|
|
1905
|
-
console.log(chalk.dim(`
|
|
1906
|
-
console.log(chalk.dim(`
|
|
1869
|
+
console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
|
|
1870
|
+
console.log(chalk.dim(` Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
1907
1871
|
console.log()
|
|
1908
1872
|
|
|
1909
1873
|
if (savedConfig.model === config.model) {
|
|
1910
|
-
console.log(chalk.green(`
|
|
1874
|
+
console.log(chalk.green(` Default model set to: ${modelRef}`))
|
|
1911
1875
|
} else {
|
|
1912
|
-
console.log(chalk.yellow(`
|
|
1876
|
+
console.log(chalk.yellow(` Config might not have been saved correctly`))
|
|
1913
1877
|
}
|
|
1914
1878
|
console.log()
|
|
1915
|
-
console.log(chalk.dim(' Starting OpenCode
|
|
1879
|
+
console.log(chalk.dim(' Starting OpenCode...'))
|
|
1916
1880
|
console.log()
|
|
1917
1881
|
|
|
1918
1882
|
// π Pass existing proxy to spawnOpenCode so it doesn't start a second one
|
|
@@ -1923,7 +1887,7 @@ After installation, you can use: opencode --model ${modelRef}`
|
|
|
1923
1887
|
// π Groq: built-in OpenCode provider β needs provider block with apiKey in opencode.json.
|
|
1924
1888
|
// π Cerebras: NOT built-in β needs @ai-sdk/openai-compatible + baseURL, like NVIDIA.
|
|
1925
1889
|
// π Both need the model registered in provider.<key>.models so OpenCode can find it.
|
|
1926
|
-
console.log(chalk.green(`
|
|
1890
|
+
console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default...`))
|
|
1927
1891
|
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
1928
1892
|
console.log()
|
|
1929
1893
|
|
|
@@ -1932,7 +1896,7 @@ After installation, you can use: opencode --model ${modelRef}`
|
|
|
1932
1896
|
|
|
1933
1897
|
if (existsSync(getOpenCodeConfigPath())) {
|
|
1934
1898
|
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
1935
|
-
console.log(chalk.dim(`
|
|
1899
|
+
console.log(chalk.dim(` Backup: ${backupPath}`))
|
|
1936
1900
|
}
|
|
1937
1901
|
|
|
1938
1902
|
// π Ensure the provider block exists in config β create it if missing
|
|
@@ -2069,7 +2033,7 @@ After installation, you can use: opencode --model ${modelRef}`
|
|
|
2069
2033
|
} else if (providerKey === 'cloudflare') {
|
|
2070
2034
|
const cloudflareAccountId = (process.env.CLOUDFLARE_ACCOUNT_ID || '').trim()
|
|
2071
2035
|
if (!cloudflareAccountId) {
|
|
2072
|
-
console.log(chalk.yellow('
|
|
2036
|
+
console.log(chalk.yellow(' Cloudflare Workers AI requires CLOUDFLARE_ACCOUNT_ID for OpenCode integration.'))
|
|
2073
2037
|
console.log(chalk.dim(' Export CLOUDFLARE_ACCOUNT_ID and retry this selection.'))
|
|
2074
2038
|
console.log()
|
|
2075
2039
|
return
|
|
@@ -2118,17 +2082,17 @@ After installation, you can use: opencode --model ${modelRef}`
|
|
|
2118
2082
|
saveOpenCodeConfig(config)
|
|
2119
2083
|
|
|
2120
2084
|
const savedConfig = loadOpenCodeConfig()
|
|
2121
|
-
console.log(chalk.dim(`
|
|
2122
|
-
console.log(chalk.dim(`
|
|
2085
|
+
console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
|
|
2086
|
+
console.log(chalk.dim(` Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
2123
2087
|
console.log()
|
|
2124
2088
|
|
|
2125
2089
|
if (savedConfig.model === config.model) {
|
|
2126
|
-
console.log(chalk.green(`
|
|
2090
|
+
console.log(chalk.green(` Default model set to: ${modelRef}`))
|
|
2127
2091
|
} else {
|
|
2128
|
-
console.log(chalk.yellow(`
|
|
2092
|
+
console.log(chalk.yellow(` Config might not have been saved correctly`))
|
|
2129
2093
|
}
|
|
2130
2094
|
console.log()
|
|
2131
|
-
console.log(chalk.dim(' Starting OpenCode
|
|
2095
|
+
console.log(chalk.dim(' Starting OpenCode...'))
|
|
2132
2096
|
console.log()
|
|
2133
2097
|
|
|
2134
2098
|
await spawnOpenCode(['--model', modelRef], providerKey, fcmConfig)
|
|
@@ -2159,7 +2123,7 @@ async function startOpenCodeDesktop(model, fcmConfig) {
|
|
|
2159
2123
|
}
|
|
2160
2124
|
exec(command, (err) => {
|
|
2161
2125
|
if (err) {
|
|
2162
|
-
console.error(chalk.red('
|
|
2126
|
+
console.error(chalk.red(' Could not open OpenCode Desktop'))
|
|
2163
2127
|
if (isWindows) {
|
|
2164
2128
|
console.error(chalk.dim(' Make sure OpenCode is installed from https://opencode.ai'))
|
|
2165
2129
|
} else if (isLinux) {
|
|
@@ -2174,73 +2138,60 @@ async function startOpenCodeDesktop(model, fcmConfig) {
|
|
|
2174
2138
|
|
|
2175
2139
|
if (providerKey === 'nvidia') {
|
|
2176
2140
|
// π NVIDIA NIM needs a custom provider block in OpenCode config (not built-in)
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
console.log(chalk.green(` π₯ Setting ${chalk.bold(model.label)} as default for OpenCode Desktopβ¦`))
|
|
2181
|
-
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
2182
|
-
console.log()
|
|
2141
|
+
// π Auto-create it if missing β same pattern as all other providers
|
|
2142
|
+
const config = loadOpenCodeConfig()
|
|
2143
|
+
const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
|
|
2183
2144
|
|
|
2184
|
-
|
|
2185
|
-
|
|
2145
|
+
if (existsSync(getOpenCodeConfigPath())) {
|
|
2146
|
+
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
2147
|
+
console.log(chalk.dim(` Backup: ${backupPath}`))
|
|
2148
|
+
}
|
|
2186
2149
|
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2150
|
+
// π Ensure nvidia provider block exists β auto-create if missing
|
|
2151
|
+
if (!config.provider) config.provider = {}
|
|
2152
|
+
if (!config.provider.nvidia) {
|
|
2153
|
+
config.provider.nvidia = {
|
|
2154
|
+
npm: '@ai-sdk/openai-compatible',
|
|
2155
|
+
name: 'NVIDIA NIM',
|
|
2156
|
+
options: {
|
|
2157
|
+
baseURL: 'https://integrate.api.nvidia.com/v1',
|
|
2158
|
+
apiKey: '{env:NVIDIA_API_KEY}'
|
|
2159
|
+
},
|
|
2160
|
+
models: {}
|
|
2190
2161
|
}
|
|
2162
|
+
console.log(chalk.green(' + Auto-configured NVIDIA NIM provider in OpenCode'))
|
|
2163
|
+
}
|
|
2191
2164
|
|
|
2192
|
-
|
|
2165
|
+
console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default for OpenCode Desktop...`))
|
|
2166
|
+
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
2167
|
+
console.log()
|
|
2193
2168
|
|
|
2194
|
-
|
|
2195
|
-
if (!config.provider.nvidia.models) config.provider.nvidia.models = {}
|
|
2196
|
-
config.provider.nvidia.models[ocModelId] = { name: model.label }
|
|
2197
|
-
}
|
|
2169
|
+
config.model = modelRef
|
|
2198
2170
|
|
|
2199
|
-
|
|
2171
|
+
// π Register the model in the nvidia provider's models section
|
|
2172
|
+
if (!config.provider.nvidia.models) config.provider.nvidia.models = {}
|
|
2173
|
+
config.provider.nvidia.models[ocModelId] = { name: model.label }
|
|
2200
2174
|
|
|
2201
|
-
|
|
2202
|
-
console.log(chalk.dim(` π Config saved to: ${getOpenCodeConfigPath()}`))
|
|
2203
|
-
console.log(chalk.dim(` π Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
2204
|
-
console.log()
|
|
2175
|
+
saveOpenCodeConfig(config)
|
|
2205
2176
|
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
}
|
|
2211
|
-
console.log()
|
|
2212
|
-
console.log(chalk.dim(' Opening OpenCode Desktopβ¦'))
|
|
2213
|
-
console.log()
|
|
2177
|
+
const savedConfig = loadOpenCodeConfig()
|
|
2178
|
+
console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
|
|
2179
|
+
console.log(chalk.dim(` Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
2180
|
+
console.log()
|
|
2214
2181
|
|
|
2215
|
-
|
|
2182
|
+
if (savedConfig.model === config.model) {
|
|
2183
|
+
console.log(chalk.green(` Default model set to: ${modelRef}`))
|
|
2216
2184
|
} else {
|
|
2217
|
-
console.log(chalk.yellow(
|
|
2218
|
-
console.log(chalk.dim(' Please configure it first. Config is shared between CLI and Desktop.'))
|
|
2219
|
-
console.log()
|
|
2220
|
-
|
|
2221
|
-
const configPath = getOpenCodeConfigPath()
|
|
2222
|
-
const installPrompt = `Add this to ${configPath}:
|
|
2223
|
-
|
|
2224
|
-
{
|
|
2225
|
-
"provider": {
|
|
2226
|
-
"nvidia": {
|
|
2227
|
-
"npm": "@ai-sdk/openai-compatible",
|
|
2228
|
-
"name": "NVIDIA NIM",
|
|
2229
|
-
"options": {
|
|
2230
|
-
"baseURL": "https://integrate.api.nvidia.com/v1",
|
|
2231
|
-
"apiKey": "{env:NVIDIA_API_KEY}"
|
|
2232
|
-
}
|
|
2185
|
+
console.log(chalk.yellow(` Config might not have been saved correctly`))
|
|
2233
2186
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
2187
|
+
console.log()
|
|
2188
|
+
console.log(chalk.dim(' Opening OpenCode Desktop...'))
|
|
2189
|
+
console.log()
|
|
2236
2190
|
|
|
2237
|
-
|
|
2238
|
-
console.log(chalk.cyan(installPrompt))
|
|
2239
|
-
console.log()
|
|
2240
|
-
}
|
|
2191
|
+
await launchDesktop()
|
|
2241
2192
|
} else {
|
|
2242
2193
|
if (providerKey === 'replicate') {
|
|
2243
|
-
console.log(chalk.yellow('
|
|
2194
|
+
console.log(chalk.yellow(' Replicate models are monitor-only for now in OpenCode Desktop mode.'))
|
|
2244
2195
|
console.log(chalk.dim(' Reason: Replicate uses /v1/predictions instead of OpenAI chat-completions.'))
|
|
2245
2196
|
console.log(chalk.dim(' You can still benchmark this model in the TUI and use other providers for Desktop launch.'))
|
|
2246
2197
|
console.log()
|
|
@@ -2250,7 +2201,7 @@ ${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_k
|
|
|
2250
2201
|
// π ZAI: Desktop mode can't use the localhost proxy (Desktop is a standalone app).
|
|
2251
2202
|
// π Direct the user to use OpenCode CLI mode instead, which supports ZAI via proxy.
|
|
2252
2203
|
if (providerKey === 'zai') {
|
|
2253
|
-
console.log(chalk.yellow('
|
|
2204
|
+
console.log(chalk.yellow(' ZAI models are supported in OpenCode CLI mode only (not Desktop).'))
|
|
2254
2205
|
console.log(chalk.dim(' Reason: ZAI requires a localhost proxy that only works with the CLI spawn.'))
|
|
2255
2206
|
console.log(chalk.dim(' Use OpenCode CLI mode (default) to launch ZAI models.'))
|
|
2256
2207
|
console.log()
|
|
@@ -2260,7 +2211,7 @@ ${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_k
|
|
|
2260
2211
|
// π Groq: built-in OpenCode provider β needs provider block with apiKey in opencode.json.
|
|
2261
2212
|
// π Cerebras: NOT built-in β needs @ai-sdk/openai-compatible + baseURL, like NVIDIA.
|
|
2262
2213
|
// π Both need the model registered in provider.<key>.models so OpenCode can find it.
|
|
2263
|
-
console.log(chalk.green(`
|
|
2214
|
+
console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default for OpenCode Desktop...`))
|
|
2264
2215
|
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
2265
2216
|
console.log()
|
|
2266
2217
|
|
|
@@ -2269,7 +2220,7 @@ ${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_k
|
|
|
2269
2220
|
|
|
2270
2221
|
if (existsSync(getOpenCodeConfigPath())) {
|
|
2271
2222
|
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
2272
|
-
console.log(chalk.dim(`
|
|
2223
|
+
console.log(chalk.dim(` Backup: ${backupPath}`))
|
|
2273
2224
|
}
|
|
2274
2225
|
|
|
2275
2226
|
// π Ensure the provider block exists in config β create it if missing
|
|
@@ -2396,17 +2347,17 @@ ${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_k
|
|
|
2396
2347
|
saveOpenCodeConfig(config)
|
|
2397
2348
|
|
|
2398
2349
|
const savedConfig = loadOpenCodeConfig()
|
|
2399
|
-
console.log(chalk.dim(`
|
|
2400
|
-
console.log(chalk.dim(`
|
|
2350
|
+
console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
|
|
2351
|
+
console.log(chalk.dim(` Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
2401
2352
|
console.log()
|
|
2402
2353
|
|
|
2403
2354
|
if (savedConfig.model === config.model) {
|
|
2404
|
-
console.log(chalk.green(`
|
|
2355
|
+
console.log(chalk.green(` Default model set to: ${modelRef}`))
|
|
2405
2356
|
} else {
|
|
2406
|
-
console.log(chalk.yellow(`
|
|
2357
|
+
console.log(chalk.yellow(` Config might not have been saved correctly`))
|
|
2407
2358
|
}
|
|
2408
2359
|
console.log()
|
|
2409
|
-
console.log(chalk.dim(' Opening OpenCode Desktop
|
|
2360
|
+
console.log(chalk.dim(' Opening OpenCode Desktop...'))
|
|
2410
2361
|
console.log()
|
|
2411
2362
|
|
|
2412
2363
|
await launchDesktop()
|
|
@@ -2605,6 +2556,76 @@ function filterByTierOrExit(results, tierLetter) {
|
|
|
2605
2556
|
return filtered
|
|
2606
2557
|
}
|
|
2607
2558
|
|
|
2559
|
+
// βββ Dynamic OpenRouter free model discovery ββββββββββββββββββββββββββββββββββ
|
|
2560
|
+
// π Fetches the live list of free models from OpenRouter's public API at startup.
|
|
2561
|
+
// π Replaces the static openrouter entries in MODELS with fresh data so new free
|
|
2562
|
+
// π models appear automatically without a code update.
|
|
2563
|
+
// π Falls back silently to the static list on network failure.
|
|
2564
|
+
|
|
2565
|
+
// π Known SWE-bench scores for OpenRouter free models.
|
|
2566
|
+
// π Keyed by base model ID (without the :free suffix).
|
|
2567
|
+
// π Unknown models default to tier 'B' / '25.0%'.
|
|
2568
|
+
const OPENROUTER_TIER_MAP = {
|
|
2569
|
+
'qwen/qwen3-coder': ['S+', '70.6%'],
|
|
2570
|
+
'mistralai/devstral-2': ['S+', '72.2%'],
|
|
2571
|
+
'stepfun/step-3.5-flash': ['S+', '74.4%'],
|
|
2572
|
+
'deepseek/deepseek-r1-0528': ['S', '61.0%'],
|
|
2573
|
+
'qwen/qwen3-next-80b-a3b-instruct': ['S', '65.0%'],
|
|
2574
|
+
'openai/gpt-oss-120b': ['S', '60.0%'],
|
|
2575
|
+
'openai/gpt-oss-20b': ['A', '42.0%'],
|
|
2576
|
+
'nvidia/nemotron-3-nano-30b-a3b': ['A', '43.0%'],
|
|
2577
|
+
'meta-llama/llama-3.3-70b-instruct': ['A-', '39.5%'],
|
|
2578
|
+
'mimo-v2-flash': ['A', '45.0%'],
|
|
2579
|
+
'google/gemma-3-27b-it': ['A-', '36.0%'],
|
|
2580
|
+
'google/gemma-3-12b-it': ['B+', '30.0%'],
|
|
2581
|
+
'google/gemma-3-4b-it': ['B', '22.0%'],
|
|
2582
|
+
'google/gemma-3n-e4b-it': ['B', '22.0%'],
|
|
2583
|
+
'google/gemma-3n-e2b-it': ['B', '18.0%'],
|
|
2584
|
+
'meta-llama/llama-3.2-3b-instruct': ['B', '20.0%'],
|
|
2585
|
+
'mistralai/mistral-small-3.1-24b-instruct': ['A-', '35.0%'],
|
|
2586
|
+
'qwen/qwen3-4b': ['B', '22.0%'],
|
|
2587
|
+
'nousresearch/hermes-3-llama-3.1-405b': ['A', '40.0%'],
|
|
2588
|
+
'nvidia/nemotron-nano-9b-v2': ['B+', '28.0%'],
|
|
2589
|
+
'nvidia/nemotron-nano-12b-v2-vl': ['B+', '30.0%'],
|
|
2590
|
+
'z-ai/glm-4.5-air': ['A-', '38.0%'],
|
|
2591
|
+
'arcee-ai/trinity-large-preview': ['A', '40.0%'],
|
|
2592
|
+
'arcee-ai/trinity-mini': ['B+', '28.0%'],
|
|
2593
|
+
'upstage/solar-pro-3': ['A-', '35.0%'],
|
|
2594
|
+
'cognitivecomputations/dolphin-mistral-24b-venice-edition': ['B+', '28.0%'],
|
|
2595
|
+
'liquid/lfm-2.5-1.2b-thinking': ['B', '18.0%'],
|
|
2596
|
+
'liquid/lfm-2.5-1.2b-instruct': ['B', '18.0%'],
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
async function fetchOpenRouterFreeModels() {
|
|
2600
|
+
try {
|
|
2601
|
+
const controller = new AbortController()
|
|
2602
|
+
const timeout = setTimeout(() => controller.abort(), 5000)
|
|
2603
|
+
const res = await fetch('https://openrouter.ai/api/v1/models', {
|
|
2604
|
+
signal: controller.signal,
|
|
2605
|
+
headers: {
|
|
2606
|
+
'HTTP-Referer': 'https://github.com/vava-nessa/free-coding-models',
|
|
2607
|
+
'X-Title': 'free-coding-models',
|
|
2608
|
+
},
|
|
2609
|
+
})
|
|
2610
|
+
clearTimeout(timeout)
|
|
2611
|
+
if (!res.ok) return null
|
|
2612
|
+
const json = await res.json()
|
|
2613
|
+
if (!json.data || !Array.isArray(json.data)) return null
|
|
2614
|
+
|
|
2615
|
+
const freeModels = json.data.filter(m => m.id && m.id.endsWith(':free'))
|
|
2616
|
+
|
|
2617
|
+
return freeModels.map(m => {
|
|
2618
|
+
const baseId = m.id.replace(/:free$/, '')
|
|
2619
|
+
const [tier, swe] = OPENROUTER_TIER_MAP[baseId] || ['B', '25.0%']
|
|
2620
|
+
const ctx = formatCtxWindow(m.context_length)
|
|
2621
|
+
const label = labelFromId(m.id)
|
|
2622
|
+
return [m.id, label, tier, swe, ctx]
|
|
2623
|
+
})
|
|
2624
|
+
} catch {
|
|
2625
|
+
return null
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2608
2629
|
async function main() {
|
|
2609
2630
|
const cliArgs = parseArgs(process.argv)
|
|
2610
2631
|
|
|
@@ -2670,7 +2691,11 @@ async function main() {
|
|
|
2670
2691
|
}
|
|
2671
2692
|
|
|
2672
2693
|
// π Auto-update system: force updates and handle changelog automatically
|
|
2673
|
-
|
|
2694
|
+
// π Skip when running from source (dev mode) β .git means we're in a repo checkout,
|
|
2695
|
+
// π not a global npm install. Auto-update would overwrite the global copy but restart
|
|
2696
|
+
// π the local one, causing an infinite update loop since LOCAL_VERSION never changes.
|
|
2697
|
+
const isDevMode = existsSync(join(dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1')), '..', '.git'))
|
|
2698
|
+
if (latestVersion && !isDevMode) {
|
|
2674
2699
|
console.log()
|
|
2675
2700
|
console.log(chalk.bold.red(' β AUTO-UPDATE AVAILABLE'))
|
|
2676
2701
|
console.log(chalk.red(` Version ${latestVersion} will be installed automatically`))
|
|
@@ -2700,6 +2725,23 @@ async function main() {
|
|
|
2700
2725
|
return // runUpdate will restart the process
|
|
2701
2726
|
}
|
|
2702
2727
|
|
|
2728
|
+
// π Dynamic OpenRouter free model discovery β fetch live free models from API
|
|
2729
|
+
// π Replaces static openrouter entries in MODELS with fresh data.
|
|
2730
|
+
// π Fallback: if fetch fails, the static list from sources.js stays intact + warning shown.
|
|
2731
|
+
const dynamicModels = await fetchOpenRouterFreeModels()
|
|
2732
|
+
if (dynamicModels) {
|
|
2733
|
+
// π Remove all existing openrouter entries from MODELS
|
|
2734
|
+
for (let i = MODELS.length - 1; i >= 0; i--) {
|
|
2735
|
+
if (MODELS[i][5] === 'openrouter') MODELS.splice(i, 1)
|
|
2736
|
+
}
|
|
2737
|
+
// π Push fresh entries with 'openrouter' providerKey
|
|
2738
|
+
for (const [modelId, label, tier, swe, ctx] of dynamicModels) {
|
|
2739
|
+
MODELS.push([modelId, label, tier, swe, ctx, 'openrouter'])
|
|
2740
|
+
}
|
|
2741
|
+
} else {
|
|
2742
|
+
console.log(chalk.yellow(' OpenRouter: using cached model list (live fetch failed)'))
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2703
2745
|
// π Build results from MODELS β only include enabled providers
|
|
2704
2746
|
// π Each result gets providerKey so ping() knows which URL + API key to use
|
|
2705
2747
|
|
|
@@ -2907,7 +2949,7 @@ async function main() {
|
|
|
2907
2949
|
const selectedProviderKey = providerKeys[Math.min(state.settingsCursor, providerKeys.length - 1)]
|
|
2908
2950
|
const selectedSource = sources[selectedProviderKey]
|
|
2909
2951
|
const selectedMeta = PROVIDER_METADATA[selectedProviderKey] || {}
|
|
2910
|
-
if (selectedSource && state.settingsCursor <
|
|
2952
|
+
if (selectedSource && state.settingsCursor < providerKeys.length) {
|
|
2911
2953
|
const selectedKey = getApiKey(state.config, selectedProviderKey)
|
|
2912
2954
|
const setupStatus = selectedKey ? chalk.green('API key detected β
') : chalk.yellow('API key missing β ')
|
|
2913
2955
|
lines.push(` ${chalk.bold('Setup Instructions')} β ${selectedMeta.label || selectedSource.name || selectedProviderKey}`)
|
package/lib/utils.js
CHANGED
|
@@ -468,6 +468,25 @@ function parseCtxToK(ctx) {
|
|
|
468
468
|
return 0
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
+
// π formatCtxWindow: Convert context_length number to compact string (256000 β '256k', 1048576 β '1M')
|
|
472
|
+
// π Used by dynamic OpenRouter model discovery to convert API response to our display format.
|
|
473
|
+
export function formatCtxWindow(n) {
|
|
474
|
+
if (typeof n !== 'number' || n <= 0) return '128k'
|
|
475
|
+
if (n >= 1_000_000) return Math.round(n / 1_000_000) + 'M'
|
|
476
|
+
return Math.round(n / 1000) + 'k'
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// π labelFromId: Build a human-readable label from an OpenRouter model ID.
|
|
480
|
+
// π 'qwen/qwen3-coder:free' β 'Qwen3 Coder'
|
|
481
|
+
export function labelFromId(id) {
|
|
482
|
+
const base = id.replace(/:free$/, '')
|
|
483
|
+
const name = base.includes('/') ? base.split('/').pop() : base
|
|
484
|
+
return name
|
|
485
|
+
.split(/[-_]/)
|
|
486
|
+
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
487
|
+
.join(' ')
|
|
488
|
+
}
|
|
489
|
+
|
|
471
490
|
// π parseSweToNum: Convert SWE-bench score string ("49.2%", "73.1%") into a 0β100 number.
|
|
472
491
|
// π Returns 0 for missing or invalid scores.
|
|
473
492
|
function parseSweToNum(sweScore) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.81",
|
|
4
4
|
"description": "Find the fastest coding LLM models in seconds β ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nvidia",
|