llm-switcher 1.2.5 → 1.2.6

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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog — LLM Switcher
2
2
 
3
+ ## Release 1.2.6
4
+
5
+ - **Deactivate in the dashboard:** The Deactivate button did not change the configuration, but the dashboard showed "deactivated". The dashboard sends `deactivate: true` and the profile key in `profile`. The gateway read `deactivate` as the profile key, so no target matched. Now `POST /api/switch` accepts both forms: `{"profile": "x", "deactivate": true}` and `{"deactivate": "x"}`.
6
+ - **No silent success:** If the profile is not active, `POST /api/switch` now answers `404` with the error `Profile "x" is not active`. If `deactivate` is `true` and no profile is named, it answers `400`. Before, both requests answered `200` with `success: true`.
7
+
3
8
  ## Release 1.2.5
4
9
 
5
10
  - **Dashboard controls:** The dashboard controls use MIT elements from Uiverse.io galaxy as a base. The CSS gives the name of each author.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-switcher",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "Zero-dependency multi-protocol edge gateway & provider switcher for Claude Code, Codex, OpenAI and Gemini clients",
5
5
  "keywords": [
6
6
  "llm",
package/proxy.mjs CHANGED
@@ -1367,7 +1367,12 @@ async function routeConfigApi(res, method, pathname, body) {
1367
1367
  if (pathname === '/api/switch') {
1368
1368
  let err = null;
1369
1369
  if (body.deactivate) {
1370
- deactivateProfile(cfg, body.deactivate);
1370
+ // The dashboard sends `deactivate: true` with the key in `profile`; the CLI sends the key itself.
1371
+ const key = body.deactivate === true ? body.profile : body.deactivate;
1372
+ if (typeof key !== 'string' || !key) return sendJson(res, 400, { error: 'deactivate needs a profile key' });
1373
+ // A no-op must not answer success: the dashboard trusts `success` alone.
1374
+ if (!isProfileActive(cfg, key)) return sendJson(res, 404, { error: `Profile "${key}" is not active` });
1375
+ deactivateProfile(cfg, key);
1371
1376
  } else if (body.target) {
1372
1377
  err = setTargetProfile(cfg, body.target, body.profile || null);
1373
1378
  } else if (body.profile) {
package/switch.cmd CHANGED
@@ -1,2 +1,2 @@
1
- @echo off
2
- node "%~dp0switch.mjs" %*
1
+ @echo off
2
+ node "%~dp0switch.mjs" %*
@@ -1208,3 +1208,30 @@ test('Admin test-upstream reports latency and a sample from the upstream', async
1208
1208
  assert.match(json.sample, /Final answer/);
1209
1209
  assert.equal(typeof json.latency, 'number');
1210
1210
  });
1211
+
1212
+ // Runs last: turning `codex` off and on stops and restarts the shared interceptor, which the
1213
+ // Codex WebSocket tests above depend on.
1214
+ test('/api/switch deactivate: the dashboard payload works, the CLI payload works, a no-op is an error', async () => {
1215
+ try {
1216
+ // What the dashboard sends: the profile in `profile`, a boolean in `deactivate`.
1217
+ const dash = await post('/api/switch', { profile: 'chat', deactivate: true });
1218
+ assert.equal(dash.status, 200);
1219
+ assert.deepEqual((await dash.json()).activeProfiles, { claude: null, codex: null });
1220
+
1221
+ // The same profile is now inactive, so asking again must not report success.
1222
+ const again = await post('/api/switch', { profile: 'chat', deactivate: true });
1223
+ assert.equal(again.status, 404);
1224
+ assert.match((await again.json()).error, /not active/);
1225
+
1226
+ // What the CLI sends: the profile key in `deactivate`.
1227
+ assert.equal((await post('/api/switch', { profile: 'chat' })).status, 200);
1228
+ const cli = await post('/api/switch', { deactivate: 'chat' });
1229
+ assert.equal(cli.status, 200);
1230
+ assert.deepEqual((await cli.json()).activeProfiles, { claude: null, codex: null });
1231
+
1232
+ // `deactivate: true` with no profile names nothing.
1233
+ assert.equal((await post('/api/switch', { deactivate: true })).status, 400);
1234
+ } finally {
1235
+ await post('/api/switch', { profile: 'chat' });
1236
+ }
1237
+ });