amicus 4.1.1 → 4.1.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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +19 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/sidecar/setup.js +2 -3
- package/src/utils/config.js +29 -9
- package/src/utils/quick-picks.js +35 -12
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amicus",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"description": "Multi-model LLM Council + parallel AI window for Claude Code. Run structured council reviews across Gemini, GPT, DeepSeek and more — or fork a conversation to any model and fold the results back.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Christian Wagner"
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ All notable changes to Amicus are documented here. Format follows
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [4.1.2] - 2026-07-22
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Claude model aliases no longer drift between the direct API and OpenRouter.** The two
|
|
13
|
+
gateways spell the same model differently — OpenRouter serves `anthropic/claude-opus-4.8`,
|
|
14
|
+
the direct Anthropic API serves `anthropic/claude-opus-4-8` — and three places converted
|
|
15
|
+
between the two by adding or removing the `openrouter/` prefix. That is only sound when the
|
|
16
|
+
rest of the id is identical, which for Claude it is not. Two user-visible consequences: a fresh
|
|
17
|
+
`amicus setup` wrote an `opus` alias that `amicus doctor` then reported as stale (the exact
|
|
18
|
+
warning 4.1.1 shipped to remove — and it fired even with an Anthropic-only catalog, not just
|
|
19
|
+
for OpenRouter users), and the pre-registered fallback catalog handed to a long-lived shared
|
|
20
|
+
server carried two ids OpenRouter does not serve while omitting the two it does. All three
|
|
21
|
+
sites now read each gateway's authored route instead of deriving one from the other, and an
|
|
22
|
+
alias you have overridden yourself is left alone rather than inheriting a curated route.
|
|
23
|
+
Affects `opus` and `haiku`; every other alias is spelled identically on both gateways or is
|
|
24
|
+
OpenRouter-only. Default routing is direct-first and was never affected — no run selected a
|
|
25
|
+
wrong model.
|
|
26
|
+
|
|
8
27
|
## [4.1.1] - 2026-07-21
|
|
9
28
|
|
|
10
29
|
### Fixed
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amicus",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"mcpName": "io.github.BourbonDog/amicus",
|
|
5
5
|
"description": "Multi-model LLM Council + parallel AI window for Claude Code. Run structured council reviews across Gemini, GPT, DeepSeek and more — or fork a conversation to any model and fold the results back.",
|
|
6
6
|
"keywords": [
|
package/src/sidecar/setup.js
CHANGED
|
@@ -353,8 +353,7 @@ async function runReadlineSetup() {
|
|
|
353
353
|
return;
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
const { resolveQuickPicks, toLiveSeedAliases } = require('../utils/quick-picks');
|
|
357
|
-
const { toCanonicalDefault } = require('../utils/curated-models');
|
|
356
|
+
const { resolveQuickPicks, toLiveSeedAliases, toStorableRoute } = require('../utils/quick-picks');
|
|
358
357
|
const picks = resolveQuickPicks(catalog);
|
|
359
358
|
|
|
360
359
|
console.log('Choose your default model:');
|
|
@@ -386,7 +385,7 @@ async function runReadlineSetup() {
|
|
|
386
385
|
// choice), but the alias's VALUE must stay the vendor phase's tier choice --
|
|
387
386
|
// skip the curated-flagship upgrade so it isn't discarded.
|
|
388
387
|
if (pick && !chosen.noUpgrade && !vendorAliasesWritten.has(chosen.alias)) {
|
|
389
|
-
cfg.aliases[chosen.alias] =
|
|
388
|
+
cfg.aliases[chosen.alias] = toStorableRoute(pick);
|
|
390
389
|
} else if (cfg.aliases[chosen.alias] === undefined) {
|
|
391
390
|
const fallback = getDefaultAliases()[chosen.alias];
|
|
392
391
|
if (fallback !== undefined) { cfg.aliases[chosen.alias] = fallback; }
|
package/src/utils/config.js
CHANGED
|
@@ -12,8 +12,9 @@ const { autoRepairAlias } = require('./alias-resolver');
|
|
|
12
12
|
const { isDirectProvider } = require('./provider-registry');
|
|
13
13
|
|
|
14
14
|
/** Default model alias map — derived from the curated-models single source (F5) */
|
|
15
|
-
const { toDefaultAliases } = require('./curated-models');
|
|
15
|
+
const { toDefaultAliases, toGatewayRoutes } = require('./curated-models');
|
|
16
16
|
const DEFAULT_ALIASES = toDefaultAliases();
|
|
17
|
+
const CURATED_ROUTES = toGatewayRoutes();
|
|
17
18
|
|
|
18
19
|
/** Built-in council benches (B23) — consulted only when a name is absent from user config. */
|
|
19
20
|
const { resolveBuiltinCouncil } = require('./council-presets');
|
|
@@ -261,10 +262,19 @@ function tryResolveModel(modelArg) {
|
|
|
261
262
|
* threading only that first session's resolved id is insufficient. So for
|
|
262
263
|
* every alias that resolves to a BARE direct-capable-vendor id (post-#61
|
|
263
264
|
* default aliases are bare, e.g. `openai/gpt-5.5`), this ALSO registers the
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
265
|
+
* alias's OpenRouter form — broadening the catalog to cover BOTH routes the
|
|
266
|
+
* router might pick, regardless of which session created the server. This
|
|
267
|
+
* does NOT change what the alias itself resolves to (still bare, still
|
|
268
|
+
* direct-first) — it only widens what's pre-registered.
|
|
269
|
+
*
|
|
270
|
+
* The mirror is read from the alias's authored gateway route, NOT built by
|
|
271
|
+
* prepending `openrouter/`. For DIVERGENT_VENDORS the two ids are different
|
|
272
|
+
* strings, not merely differently prefixed — OpenRouter serves
|
|
273
|
+
* `anthropic/claude-opus-4.8` while the direct API serves
|
|
274
|
+
* `anthropic/claude-opus-4-8`. v4.1.1 made `toDefaultAliases()` emit the
|
|
275
|
+
* direct form, at which point prepending produced an id OpenRouter does not
|
|
276
|
+
* serve (fixed in v4.1.2). Aliases the user has overridden fall back to the
|
|
277
|
+
* prefix form, since no authored gateway route describes them.
|
|
268
278
|
* @param {string[]} [resolvedRoutes] executable model id(s) actually launched
|
|
269
279
|
* @returns {object} e.g. { openrouter: { models: { "x-ai/grok-4.3": {}, ... } } } */
|
|
270
280
|
function buildProviderModels(resolvedRoutes = []) {
|
|
@@ -285,13 +295,23 @@ function buildProviderModels(resolvedRoutes = []) {
|
|
|
285
295
|
providers[providerID].models[modelID] = {};
|
|
286
296
|
};
|
|
287
297
|
|
|
288
|
-
for (const fullModel of Object.
|
|
298
|
+
for (const [alias, fullModel] of Object.entries(aliases)) {
|
|
289
299
|
addRoute(fullModel);
|
|
290
300
|
|
|
291
301
|
// Broaden: a bare direct-capable-vendor route also gets an OpenRouter
|
|
292
|
-
// mirror registered (see catalog-broadening note above).
|
|
293
|
-
//
|
|
294
|
-
//
|
|
302
|
+
// mirror registered (see catalog-broadening note above). Prefer the
|
|
303
|
+
// alias's AUTHORED OpenRouter route — for divergent vendors it is a
|
|
304
|
+
// different id, not a prefixed one. Guarded on the alias still holding its
|
|
305
|
+
// shipped direct value so a user override never inherits a curated route.
|
|
306
|
+
const curated = CURATED_ROUTES[alias];
|
|
307
|
+
if (curated && curated.openrouter && curated.direct === fullModel) {
|
|
308
|
+
addRoute(curated.openrouter);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Fallback for user-defined aliases. Gateway-only aliases (already
|
|
313
|
+
// `openrouter/...`, e.g. grok/qwen/x-ai) are untouched — OpenRouter is
|
|
314
|
+
// their only possible route anyway, already covered above.
|
|
295
315
|
if (typeof fullModel === 'string' && !fullModel.startsWith('openrouter/')) {
|
|
296
316
|
const vendor = fullModel.split('/')[0];
|
|
297
317
|
if (isDirectProvider(vendor)) {
|
package/src/utils/quick-picks.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
'use strict';
|
|
12
12
|
|
|
13
|
-
const { getFamilies, toDefaultAliases, toCanonicalDefault } = require('./curated-models');
|
|
13
|
+
const { getFamilies, toDefaultAliases, toCanonicalDefault, DIVERGENT_VENDORS } = require('./curated-models');
|
|
14
14
|
|
|
15
15
|
const MARKER_RE = /(-preview|-exp|-beta|-latest|:free)+$/;
|
|
16
16
|
|
|
@@ -45,7 +45,7 @@ function pickCurrent(catalog, nsPrefix, vendorPath, idPattern) {
|
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* @param {Array<{id:string}>} catalog
|
|
48
|
-
* @returns {Array<{alias,label,blurb,source:'live'|'fallback',routes:Object<string,string>}>}
|
|
48
|
+
* @returns {Array<{alias,label,blurb,vendorPath,source:'live'|'fallback',routes:Object<string,string>}>}
|
|
49
49
|
* `routes` may be empty if a family defines no fallback and the catalog has no match.
|
|
50
50
|
*/
|
|
51
51
|
function resolveQuickPicks(catalog) {
|
|
@@ -60,30 +60,53 @@ function resolveQuickPicks(catalog) {
|
|
|
60
60
|
if (direct) { routes[p] = direct; live = true; }
|
|
61
61
|
else if (f.fallback[p]) { routes[p] = f.fallback[p]; }
|
|
62
62
|
}
|
|
63
|
-
return { alias: f.alias, label: f.label, blurb: f.blurb, routes,
|
|
63
|
+
return { alias: f.alias, label: f.label, blurb: f.blurb, vendorPath: f.vendorPath, routes,
|
|
64
64
|
source: live ? 'live' : 'fallback' };
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* The single route value a wizard may STORE for a resolved quick pick.
|
|
70
|
+
*
|
|
71
|
+
* For a direct-capable vendor the OpenRouter pick is canonicalised to bare
|
|
72
|
+
* `vendor/model` so it stays direct-first via the gateway router — otherwise a
|
|
73
|
+
* fresh `amicus setup` with a live catalog would silently defeat the
|
|
74
|
+
* direct-first default `toDefaultAliases()` establishes.
|
|
75
|
+
*
|
|
76
|
+
* For a DIVERGENT vendor the direct id is never DERIVED from the OpenRouter
|
|
77
|
+
* one: they are different strings, not differently prefixed (OpenRouter serves
|
|
78
|
+
* `anthropic/claude-opus-4.8`, the direct API `anthropic/claude-opus-4-8`).
|
|
79
|
+
* Stripping the prefix there fabricates an id the direct API rejects, which
|
|
80
|
+
* `amicus doctor` then reports as a stale alias. The row's own direct route is
|
|
81
|
+
* used verbatim, falling back to the intact `openrouter/` form when the
|
|
82
|
+
* catalog offered no direct pick. Mirrors the guard already used at
|
|
83
|
+
* `provider-default-picker.js:82,143,220`.
|
|
84
|
+
* @param {{vendorPath?:string, routes?:Object<string,string>}} pick
|
|
85
|
+
* @returns {string|undefined}
|
|
86
|
+
*/
|
|
87
|
+
function toStorableRoute(pick) {
|
|
88
|
+
const routes = (pick && pick.routes) || {};
|
|
89
|
+
if (pick && DIVERGENT_VENDORS.has(pick.vendorPath)) {
|
|
90
|
+
return routes[pick.vendorPath] || routes.openrouter;
|
|
91
|
+
}
|
|
92
|
+
return toCanonicalDefault(routes.openrouter || Object.values(routes)[0]);
|
|
93
|
+
}
|
|
94
|
+
|
|
68
95
|
/**
|
|
69
96
|
* Seed map for fresh configs: static defaults overlaid with live family
|
|
70
|
-
* routes (cardless aliases stay pinned).
|
|
71
|
-
*
|
|
72
|
-
* lands as bare `vendor/model` (direct-first via the gateway router)
|
|
73
|
-
* instead of the raw `openrouter/<vendor>/<rest>` pick — otherwise a fresh
|
|
74
|
-
* `amicus setup` with a live catalog would silently defeat the direct-first
|
|
75
|
-
* default `toDefaultAliases()` establishes. Gateway-only vendors are
|
|
76
|
-
* returned unchanged by `toCanonicalDefault`.
|
|
97
|
+
* routes (cardless aliases stay pinned). See `toStorableRoute` for why the
|
|
98
|
+
* overlaid value is not a raw prefix strip.
|
|
77
99
|
* @returns {Object<string,string>}
|
|
78
100
|
*/
|
|
79
101
|
function toLiveSeedAliases(catalog) {
|
|
80
102
|
const seeds = toDefaultAliases();
|
|
81
103
|
for (const r of resolveQuickPicks(catalog || [])) {
|
|
82
104
|
if (r.source === 'live' && r.routes.openrouter) {
|
|
83
|
-
|
|
105
|
+
const stored = toStorableRoute(r);
|
|
106
|
+
if (stored) { seeds[r.alias] = stored; }
|
|
84
107
|
}
|
|
85
108
|
}
|
|
86
109
|
return seeds;
|
|
87
110
|
}
|
|
88
111
|
|
|
89
|
-
module.exports = { compareIdsDesc, pickCurrent, resolveQuickPicks, toLiveSeedAliases };
|
|
112
|
+
module.exports = { compareIdsDesc, pickCurrent, resolveQuickPicks, toLiveSeedAliases, toStorableRoute };
|