free-coding-models 0.5.77 → 0.5.78

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.
@@ -0,0 +1,17 @@
1
+ # Changelog v0.5.78 - 2026-08-23
2
+
3
+ ### Fixed
4
+ - **Router forwards `prompt_cache_key` to NVIDIA -> HTTP 400 (fixes #160).** Clients using OpenAI JS SDK (pi agent) set `prompt_cache_key` to session UUID for prompt caching; NVIDIA rejects it with `Unsupported parameter(s): prompt_cache_key`. Added `prompt_cache_key` to `STRIP_PARAMS` in `src/core/schema-normalizer.js` and added `nvidia: normalizeNvidia` (strip + orphan tool drop) to `PROVIDER_NORMALIZERS` so `normalizeRequestBody(body, 'nvidia')` now strips the hint before forwarding. Verified with direct replay against `integrate.api.nvidia.com` and `pnpm test` 813/813. Thanks @pelvity.
5
+
6
+ - **Config `pingInterval` not respected at startup (fixes #155).** TUI always started in speed mode (2s) ignoring `settings.pingInterval` and `--ping-interval`. Now `src/tui/tui-state.js` `createTuiState()` derives `initialInterval` from `config.settings.pingInterval` (already merged from CLI in `src/tui/app.js:283`) and maps to mode via `intervalToPingMode` (30000 -> slow, 10000 -> normal, 4000 -> forced, <=3000 -> speed). Sets `pingModeSource` to `config` vs `startup` and only sets `speedModeUntil` when initial mode is speed. Users with 30s interval no longer burn quota on startup burst. Thanks @BetterToAutomateTheWorld.
7
+
8
+ - **jcode configuration option for CLI and web (fixes #145).** CLI Z cycle already had `jcode` but launch hung (stdin left in raw mode, REPL appeared frozen) and web had no jcode option at all.
9
+ - CLI hang: `src/tui/key-handler.js` `launchSelectedModel` now calls `stopUi({ resetRawMode: true })` (was `stopUi()` with raw mode still enabled) and `src/core/tool-launchers.js` `spawnCommand` defensively resets `stdin.setRawMode(false)` before handing TTY to child.
10
+ - Web: Added `jcode` to `INSTALL_TARGET_MODES` in `src/core/endpoint-installer.js` (now 15 targets, `jcode` after `pi` to match `TOOL_MODE_ORDER` order) with env-based installer (`~/.fcm-jcode-env`), mirrored in `web/src/utils/m3.js` `INSTALL_ENDPOINT_TOOL_MODES`, and fixed `web/src/hooks/useToolMode.js` cycle bug (`TOOL_MODE_ORDER` -> `INSTALL_ENDPOINT_TOOL_MODES`). Web `/api/tool-mode` now accepts `jcode` without 422 and dropdown/cycle shows it. Thanks @plahakps.
11
+
12
+ ### Changed
13
+ - `src/core/schema-normalizer.js` JSDoc now exports `normalizeNvidia` and clarifies per-provider normalization.
14
+ - `web/src/utils/m3.js` and `src/core/endpoint-installer.js` tool order now matches CLI's `TOOL_MODE_ORDER` (opencode -> pi -> jcode -> opencode-desktop ...).
15
+
16
+ ### Tests
17
+ - 813/813 passing. Updated `test/test.js` endpoint install target expectation to include `jcode`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.5.77",
3
+ "version": "0.5.78",
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",
@@ -53,8 +53,8 @@ import { ensureDir, readJson as sharedReadJson } from './shared-helpers.js'
53
53
  // 📖 zai and opencode-zen ARE OpenAI-compatible and CAN be installed into any tool.
54
54
  const DIRECT_INSTALL_UNSUPPORTED_PROVIDERS = new Set(['replicate'])
55
55
  // 📖 Install Endpoints only lists tools whose persisted config shape is actually supported here.
56
- // 📖 Launch-only tools stay out: the Web dashboard configures endpoints, it never starts CLIs.
57
- const INSTALL_TARGET_MODES = ['opencode', 'opencode-desktop', 'opencode-web', 'openclaw', 'crush', 'goose', 'pi', 'aider', 'qwen', 'openhands', 'amp', 'forgecode', 'fcm_router', 'zcode']
56
+ // 📖 jcode is CLI-only and uses an env-file helper (same as openhands) selectable in both CLI and Web.
57
+ const INSTALL_TARGET_MODES = ['opencode', 'pi', 'jcode', 'opencode-desktop', 'opencode-web', 'openclaw', 'crush', 'goose', 'aider', 'qwen', 'openhands', 'amp', 'forgecode', 'fcm_router', 'zcode']
58
58
 
59
59
  function getDefaultPaths() {
60
60
  const home = homedir()
@@ -839,6 +839,8 @@ export function installProviderEndpoints(config, providerKey, toolMode, options
839
839
  installResult = installIntoAmp(providerKey, models, apiKey, paths)
840
840
  } else if (canonicalToolMode === 'qwen') {
841
841
  installResult = installIntoQwen(providerKey, models, apiKey, paths)
842
+ } else if (canonicalToolMode === 'jcode') {
843
+ installResult = installIntoEnvBasedTool(providerKey, models, apiKey, canonicalToolMode, paths)
842
844
  } else if (canonicalToolMode === 'openhands') {
843
845
  installResult = installIntoEnvBasedTool(providerKey, models, apiKey, canonicalToolMode, paths)
844
846
  } else if (canonicalToolMode === 'fcm_router') {
@@ -30,8 +30,9 @@
30
30
  * → `clampTemperature` — clamps `temperature` to the provider's accepted range
31
31
  * → `normalizeZai` — for `zai` (GLM) provider
32
32
  * → `normalizeMistral` — for `mistral` and `codestral` providers
33
+ * → `normalizeNvidia` — for `nvidia` provider (strips unsupported params like prompt_cache_key)
33
34
  *
34
- * @exports normalizeRequestBody, normalizeZai, normalizeMistral, PROVIDER_NORMALIZERS
35
+ * @exports normalizeRequestBody, normalizeZai, normalizeMistral, normalizeNvidia, PROVIDER_NORMALIZERS
35
36
  *
36
37
  * @see src/core/router-daemon.js — calls `normalizeRequestBody` before forwarding upstream
37
38
  */
@@ -48,6 +49,7 @@ const STRIP_PARAMS = [
48
49
  'echo', // not in OpenAI spec
49
50
  'user', // PII risk; not used by FCM
50
51
  'metadata', // not always supported
52
+ 'prompt_cache_key', // OpenAI prompt-caching hint; NVIDIA rejects with 400
51
53
  'store', // GLM rejects
52
54
  ]
53
55
 
@@ -146,6 +148,20 @@ export function normalizeMistral(body) {
146
148
  return result
147
149
  }
148
150
 
151
+ /**
152
+ * 📖 `normalizeNvidia` — transforms a request body for the `nvidia` provider.
153
+ *
154
+ * 1. Strips parameters NVIDIA rejects with 400 (e.g. prompt_cache_key)
155
+ * 2. Removes orphan `tool` messages that lack a matching assistant tool_call
156
+ * 3. Strips `stream_options` when not streaming
157
+ */
158
+ export function normalizeNvidia(body) {
159
+ if (!body || typeof body !== 'object') return body
160
+ let result = stripUnsupportedParams(body)
161
+ result = dropOrphanToolMessages(result)
162
+ return result
163
+ }
164
+
149
165
  // 📖 Map of provider key → normalizer function. Providers that don't need
150
166
  // 📖 any tweak (most OpenAI-compat ones like Groq, Cerebras, etc.) are not
151
167
  // 📖 listed and pass through `normalizeRequestBody` unchanged.
@@ -153,6 +169,7 @@ export const PROVIDER_NORMALIZERS = {
153
169
  zai: normalizeZai,
154
170
  mistral: normalizeMistral,
155
171
  codestral: normalizeMistral,
172
+ nvidia: normalizeNvidia,
156
173
  }
157
174
 
158
175
  /**
@@ -209,6 +209,12 @@ const JCODE_NATIVE_PROVIDERS = {
209
209
  }
210
210
 
211
211
  function spawnCommand(command, args, env) {
212
+ // 📖 Defensive: ensure stdin is back in cooked mode before handing the TTY to
213
+ // 📖 an interactive child (jcode REPL etc). The TUI leaves raw mode enabled;
214
+ // 📖 if the caller forgot to reset it the child will appear to hang.
215
+ try {
216
+ if (process.stdin.isTTY && process.stdin.isRaw) process.stdin.setRawMode(false)
217
+ } catch {}
212
218
  return new Promise((resolve, reject) => {
213
219
  const child = spawn(command, args, {
214
220
  stdio: 'inherit',
@@ -278,9 +278,7 @@ export function createKeyHandler(ctx) {
278
278
  userSelected = { modelId: selected.modelId, label: selected.label, tier: selected.tier, providerKey: selected.providerKey, ctx: selected.ctx }
279
279
 
280
280
  if (!uiAlreadyStopped) {
281
- readline.emitKeypressEvents(process.stdin)
282
- if (process.stdin.isTTY) process.stdin.setRawMode(true)
283
- stopUi()
281
+ stopUi({ resetRawMode: true })
284
282
  }
285
283
 
286
284
  // 📖 If router is enabled, push [selected, ...favorites] to daemon as the active set
@@ -89,6 +89,13 @@ export function createTuiState({
89
89
 
90
90
  // 📖 Dynamic normal interval from config (default 10s)
91
91
  const normalInterval = config.settings?.pingInterval ?? PING_MODE_INTERVALS.normal
92
+ // 📖 Respect config.settings.pingInterval at startup (issue #155).
93
+ // 📖 CLI override is already merged into config.settings.pingInterval in app.js.
94
+ const configuredInterval = config.settings?.pingInterval
95
+ const hasConfiguredInterval =
96
+ typeof configuredInterval === 'number' && Number.isFinite(configuredInterval) && configuredInterval > 0
97
+ const initialInterval = hasConfiguredInterval ? configuredInterval : PING_MODE_INTERVALS.speed
98
+ const initialMode = intervalToPingMode(initialInterval)
92
99
 
93
100
  return {
94
101
  // 📖 Core data: model results (mutated in-place by ping loop)
@@ -107,14 +114,14 @@ export function createTuiState({
107
114
  sortDirection: (config.settings?.sortAsc ?? true) ? 'asc' : 'desc',
108
115
 
109
116
  // 📖 Ping cadence — drives the interval between background ping cycles
110
- pingInterval: PING_MODE_INTERVALS.speed,
111
- pingMode: 'speed',
112
- pingModeSource: 'startup',
117
+ pingInterval: initialInterval,
118
+ pingMode: initialMode,
119
+ pingModeSource: hasConfiguredInterval ? 'config' : 'startup',
113
120
  pingModeIntervals: {
114
121
  ...PING_MODE_INTERVALS,
115
122
  normal: normalInterval,
116
123
  },
117
- speedModeUntil: now + SPEED_MODE_DURATION_MS,
124
+ speedModeUntil: initialMode === 'speed' ? now + SPEED_MODE_DURATION_MS : null,
118
125
  lastPingTime: now,
119
126
  lastUserActivityAt: now,
120
127
  resumeSpeedOnActivity: false,