@polderlabs/bizar 10.19.1 → 10.19.3
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/cli/commands/models.mjs
CHANGED
|
@@ -418,14 +418,27 @@ export function defaultTierHint(modelId) {
|
|
|
418
418
|
return 'mid';
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
-
function classifyKind(modelId) {
|
|
421
|
+
export function classifyKind(modelId) {
|
|
422
422
|
const id = String(modelId || '').toLowerCase();
|
|
423
|
+
// Order matters: more-specific legacy prefixes are checked BEFORE the
|
|
424
|
+
// generic `claude-` catch-all so `claude-qwen/...` and
|
|
425
|
+
// `claude-minimax/...` are tagged with their real family, not lumped
|
|
426
|
+
// into the generic `claude` bucket. Live gateway prefixes follow the
|
|
427
|
+
// same rule.
|
|
428
|
+
if (id.startsWith('claude-qwen/')) return 'claude-qwen';
|
|
429
|
+
if (id.startsWith('claude-minimax/')) return 'claude-minimax';
|
|
423
430
|
if (id.startsWith('claude-')) return 'claude';
|
|
424
431
|
if (id.startsWith('cx/')) return 'cx';
|
|
425
432
|
if (id.startsWith('oc/')) return 'oc';
|
|
426
|
-
if (id.startsWith('claude-minimax/')) return 'claude-minimax';
|
|
427
|
-
if (id.startsWith('claude-qwen/')) return 'claude-qwen';
|
|
428
433
|
if (id.startsWith('anthropic/')) return 'anthropic';
|
|
434
|
+
// Live gateway namespace (10.19.2+): bare provider/model forms exposed by
|
|
435
|
+
// OmniRoute at https://route.polderlabs.io/v1.
|
|
436
|
+
if (id.startsWith('minimax/')) return 'minimax';
|
|
437
|
+
if (id.startsWith('codex/')) return 'codex';
|
|
438
|
+
if (id.startsWith('glm/')) return 'glm';
|
|
439
|
+
if (id.startsWith('qct/')) return 'qct';
|
|
440
|
+
if (id.startsWith('openrouter/')) return 'openrouter';
|
|
441
|
+
if (id.startsWith('a/')) return 'a';
|
|
429
442
|
return 'other';
|
|
430
443
|
}
|
|
431
444
|
|
|
@@ -483,6 +496,205 @@ function writeAtomic(path, body) {
|
|
|
483
496
|
renameSync(tmp, path);
|
|
484
497
|
}
|
|
485
498
|
|
|
499
|
+
// ── F-191 / 10.19.2 sync to Claude Code settings.json + stale-ID gating ─────
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Detect model IDs that the live gateway does not serve. Picker picks
|
|
503
|
+
* survive gateway 404s only when the user is warned at save time. Stale
|
|
504
|
+
* IDs are written into `userSelected.staleIds` (audit trail) and excluded
|
|
505
|
+
* from the settings.json sync so Claude Code does not see a `[claude-code:
|
|
506
|
+
* unrecognized_model]` for a known-dead ID on every turn.
|
|
507
|
+
*
|
|
508
|
+
* @param {{ liveIds: string[], pickedIds: string[] }} opts
|
|
509
|
+
* @returns {{ liveIds: string[], staleIds: string[], unknownIds: string[] }}
|
|
510
|
+
* - `liveIds` — picks that exist in the live gateway pool.
|
|
511
|
+
* - `staleIds` — picks that were never returned by the gateway in this run.
|
|
512
|
+
* - `unknownIds` — picks whose live status is unknown (no live pool yet).
|
|
513
|
+
*/
|
|
514
|
+
export function partitionStalePicks({ liveIds, pickedIds }) {
|
|
515
|
+
const live = Array.isArray(liveIds) ? new Set(liveIds) : null;
|
|
516
|
+
const picks = Array.isArray(pickedIds) ? pickedIds.filter((id) => typeof id === 'string' && id.trim()) : [];
|
|
517
|
+
if (!live || live.size === 0) {
|
|
518
|
+
return { liveIds: [], staleIds: [], unknownIds: [...new Set(picks)] };
|
|
519
|
+
}
|
|
520
|
+
const liveOut = [];
|
|
521
|
+
const stale = [];
|
|
522
|
+
for (const id of picks) {
|
|
523
|
+
if (live.has(id)) liveOut.push(id);
|
|
524
|
+
else stale.push(id);
|
|
525
|
+
}
|
|
526
|
+
return { liveIds: liveOut, staleIds: stale, unknownIds: [] };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Sync `userSelected.models` into Claude Code's settings.json under
|
|
531
|
+
* `modelOverrides` using the self-map pattern (`<id>` → `<id>`). This
|
|
532
|
+
* suppresses `[claude-code:unrecognized_model]` diagnostics on every turn
|
|
533
|
+
* for any picked ID the gateway serves. Stale IDs (not returned by the
|
|
534
|
+
* gateway) are excluded so the diagnostic still surfaces them.
|
|
535
|
+
*
|
|
536
|
+
* Behavior:
|
|
537
|
+
* - Reads `settings.json` if present; preserves every other field.
|
|
538
|
+
* - Writes `modelOverrides` as a sparse object: only picked IDs that
|
|
539
|
+
* are also in `liveIds`. Self-map pattern keeps dispatch behavior
|
|
540
|
+
* identical (Claude Code dispatches the literal ID).
|
|
541
|
+
* - Atomic replace via temp-file + rename (matches `applyModels`).
|
|
542
|
+
* - When `settingsJsonPath` is provided (tests), uses that instead of
|
|
543
|
+
* `~/.claude/settings.json`.
|
|
544
|
+
* - When `settingsJsonPath` is `null`, skips the sync entirely — used
|
|
545
|
+
* by tests that want to exercise the picker without touching Claude
|
|
546
|
+
* Code's real settings file.
|
|
547
|
+
*
|
|
548
|
+
* @param {{
|
|
549
|
+
* settingsJsonPath?: string|null,
|
|
550
|
+
* pickedIds: string[],
|
|
551
|
+
* liveIds?: string[],
|
|
552
|
+
* }} opts
|
|
553
|
+
* @returns {{
|
|
554
|
+
* wrote: boolean,
|
|
555
|
+
* syncedIds: string[],
|
|
556
|
+
* skippedStale: string[],
|
|
557
|
+
* settingsPath: string|null,
|
|
558
|
+
* }}
|
|
559
|
+
*/
|
|
560
|
+
export function applyModelOverrides({ settingsJsonPath, pickedIds, liveIds = [] }) {
|
|
561
|
+
const path = settingsJsonPath === undefined
|
|
562
|
+
? join(homedir(), '.claude', 'settings.json')
|
|
563
|
+
: settingsJsonPath;
|
|
564
|
+
if (path === null) {
|
|
565
|
+
return { wrote: false, syncedIds: [], skippedStale: [], settingsPath: null };
|
|
566
|
+
}
|
|
567
|
+
const live = new Set(Array.isArray(liveIds) ? liveIds : []);
|
|
568
|
+
const picks = Array.isArray(pickedIds) ? pickedIds.filter((id) => typeof id === 'string' && id.trim()) : [];
|
|
569
|
+
// Self-map only IDs the live gateway serves. Stale IDs intentionally stay
|
|
570
|
+
// out so the `[claude-code:unrecognized_model]` diagnostic still fires
|
|
571
|
+
// for them — the operator should re-run `bizar models` to drop them.
|
|
572
|
+
const synced = live.size === 0
|
|
573
|
+
? picks
|
|
574
|
+
: picks.filter((id) => live.has(id));
|
|
575
|
+
const skipped = live.size === 0 ? [] : picks.filter((id) => !live.has(id));
|
|
576
|
+
|
|
577
|
+
let settings = {};
|
|
578
|
+
if (existsSync(path)) {
|
|
579
|
+
try {
|
|
580
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
581
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) settings = parsed;
|
|
582
|
+
} catch {
|
|
583
|
+
// Corrupt settings.json — refuse to overwrite it; surface the sync
|
|
584
|
+
// as a no-op so `bizar models` still completes.
|
|
585
|
+
return { wrote: false, syncedIds: [], skippedStale: skipped, settingsPath: path };
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// Self-map pattern — Claude Code uses modelOverrides to suppress
|
|
589
|
+
// `[claude-code:unrecognized_model]` for any ID that maps to itself.
|
|
590
|
+
settings.modelOverrides = Object.fromEntries(synced.map((id) => [id, id]));
|
|
591
|
+
writeAtomic(path, JSON.stringify(settings, null, 2) + '\n');
|
|
592
|
+
return { wrote: true, syncedIds: synced, skippedStale: skipped, settingsPath: path };
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Derive a human-readable label for a model ID. Used to populate the
|
|
597
|
+
* `modelPicker` array in settings.json so Claude Code's `/model` picker
|
|
598
|
+
* displays picked models with something nicer than the raw `provider/name`
|
|
599
|
+
* string.
|
|
600
|
+
*
|
|
601
|
+
* minimax/MiniMax-M3 → "MiniMax M3"
|
|
602
|
+
* codex/gpt-5.6-sol → "GPT 5.6 Sol"
|
|
603
|
+
* qct/qwen3.8-max-preview → "Qwen3.8 Max Preview"
|
|
604
|
+
* openrouter/nvidia/foo:free → "nvidia/foo:free"
|
|
605
|
+
*
|
|
606
|
+
* The gateway-reported `name` (when available on the picked profile) always
|
|
607
|
+
* wins. Falls back to a title-cased rendering of the model segment.
|
|
608
|
+
*
|
|
609
|
+
* @param {string} modelId
|
|
610
|
+
* @param {object} [profile] Optional profile with `name` or `displayName`
|
|
611
|
+
* @returns {string}
|
|
612
|
+
*/
|
|
613
|
+
export function deriveModelLabel(modelId, profile) {
|
|
614
|
+
const name = profile && typeof profile.name === 'string' && profile.name.trim();
|
|
615
|
+
if (name) return name.trim();
|
|
616
|
+
const displayName = profile && typeof profile.displayName === 'string' && profile.displayName.trim();
|
|
617
|
+
if (displayName) return displayName.trim();
|
|
618
|
+
const id = String(modelId || '').trim();
|
|
619
|
+
if (!id) return '';
|
|
620
|
+
// Drop the leading provider segment (`minimax/MiniMax-M3` → `MiniMax-M3`)
|
|
621
|
+
// so the operator sees the model name, not the namespace.
|
|
622
|
+
const slash = id.indexOf('/');
|
|
623
|
+
const tail = slash >= 0 ? id.slice(slash + 1) : id;
|
|
624
|
+
// Split on word boundaries (hyphens / underscores / dots / colons / path
|
|
625
|
+
// separators) and join with spaces. Case is preserved verbatim — `gpt`
|
|
626
|
+
// stays `gpt`, `MiniMax` stays `MiniMax`, `M2.7` stays `M2.7`. Brand
|
|
627
|
+
// casing belongs to the gateway (`name` field), not us.
|
|
628
|
+
return tail
|
|
629
|
+
.replace(/[\\/]+/g, ' ')
|
|
630
|
+
.replace(/[-_]+/g, ' ')
|
|
631
|
+
.replace(/:/g, ' ')
|
|
632
|
+
.replace(/\s+/g, ' ')
|
|
633
|
+
.trim();
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Sync `userSelected.models` into Claude Code's `modelPicker` array
|
|
638
|
+
* (settings.json). The picker is what populates `/model` — `modelOverrides`
|
|
639
|
+
* alone only silences diagnostics, it does NOT add entries to the picker.
|
|
640
|
+
*
|
|
641
|
+
* Per Claude Code's settings reference: `modelPicker` is an array of
|
|
642
|
+
* `{ id, label }` entries that replaces the gateway-discovered picker
|
|
643
|
+
* contents. Scope is User-or-managed (settings.json is in scope). Each
|
|
644
|
+
* entry preserves the operator's pick order from `userSelected.models`.
|
|
645
|
+
*
|
|
646
|
+
* Behavior:
|
|
647
|
+
* - Reads settings.json; preserves every other field (env, mcpServers,
|
|
648
|
+
* permissions, hooks, etc.).
|
|
649
|
+
* - Writes `modelPicker` as an array of `{ id, label }`.
|
|
650
|
+
* - Filters out picks the live gateway rejects (same stale-ID contract as
|
|
651
|
+
* `applyModelOverrides`); the surviving picks fill the picker.
|
|
652
|
+
* - Empty pick list → writes `modelPicker: []` so the picker falls back
|
|
653
|
+
* to whatever Claude Code's defaults surface.
|
|
654
|
+
* - Atomic replace via temp-file + rename (matches `applyModels`).
|
|
655
|
+
* - Refuses to overwrite a corrupt settings.json.
|
|
656
|
+
* - When `settingsJsonPath === null`, returns a no-op (tests).
|
|
657
|
+
*
|
|
658
|
+
* @param {{
|
|
659
|
+
* settingsJsonPath?: string|null,
|
|
660
|
+
* pickedIds: string[],
|
|
661
|
+
* profiles?: Record<string, object>,
|
|
662
|
+
* liveIds?: string[],
|
|
663
|
+
* }} opts
|
|
664
|
+
* @returns {{
|
|
665
|
+
* wrote: boolean,
|
|
666
|
+
* entries: Array<{id: string, label: string}>,
|
|
667
|
+
* skippedStale: string[],
|
|
668
|
+
* settingsPath: string|null,
|
|
669
|
+
* }}
|
|
670
|
+
*/
|
|
671
|
+
export function applyModelPicker({ settingsJsonPath, pickedIds, profiles = {}, liveIds = [] }) {
|
|
672
|
+
const path = settingsJsonPath === undefined
|
|
673
|
+
? join(homedir(), '.claude', 'settings.json')
|
|
674
|
+
: settingsJsonPath;
|
|
675
|
+
if (path === null) {
|
|
676
|
+
return { wrote: false, entries: [], skippedStale: [], settingsPath: null };
|
|
677
|
+
}
|
|
678
|
+
const live = new Set(Array.isArray(liveIds) ? liveIds : []);
|
|
679
|
+
const picks = Array.isArray(pickedIds) ? pickedIds.filter((id) => typeof id === 'string' && id.trim()) : [];
|
|
680
|
+
const surviving = live.size === 0 ? picks : picks.filter((id) => live.has(id));
|
|
681
|
+
const skipped = live.size === 0 ? [] : picks.filter((id) => !live.has(id));
|
|
682
|
+
const entries = surviving.map((id) => ({ id, label: deriveModelLabel(id, profiles?.[id]) }));
|
|
683
|
+
|
|
684
|
+
let settings = {};
|
|
685
|
+
if (existsSync(path)) {
|
|
686
|
+
try {
|
|
687
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
688
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) settings = parsed;
|
|
689
|
+
} catch {
|
|
690
|
+
return { wrote: false, entries: [], skippedStale: skipped, settingsPath: path };
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
settings.modelPicker = entries;
|
|
694
|
+
writeAtomic(path, JSON.stringify(settings, null, 2) + '\n');
|
|
695
|
+
return { wrote: true, entries, skippedStale: skipped, settingsPath: path };
|
|
696
|
+
}
|
|
697
|
+
|
|
486
698
|
// ── F-190 / IMP-017 refresh + operator-override preservation ────────────────
|
|
487
699
|
|
|
488
700
|
/**
|
|
@@ -1294,8 +1506,15 @@ export async function run(name, args, isHelpRequest) {
|
|
|
1294
1506
|
process.exit(2);
|
|
1295
1507
|
}
|
|
1296
1508
|
const block = applyModels({ routerPath, models: ids, source: 'cli-set' });
|
|
1509
|
+
// Sync to Claude Code's settings.json — picks survive stale-ID
|
|
1510
|
+
// filtering only when an empty liveIds array disables it (no
|
|
1511
|
+
// candidates fetched for `--set`). The orchestrator will surface
|
|
1512
|
+
// `[claude-code:unrecognized_model]` for any ID the gateway later
|
|
1513
|
+
// rejects; the operator can re-run `bizar models` to drop them.
|
|
1514
|
+
const sync = applyModelOverrides({ pickedIds: ids, liveIds: [] });
|
|
1515
|
+
const picker = applyModelPicker({ pickedIds: ids, profiles: block.profiles || {}, liveIds: [] });
|
|
1297
1516
|
if (wantJson) {
|
|
1298
|
-
process.stdout.write(JSON.stringify({ applied: block }, null, 2) + '\n');
|
|
1517
|
+
process.stdout.write(JSON.stringify({ applied: block, sync, picker }, null, 2) + '\n');
|
|
1299
1518
|
} else {
|
|
1300
1519
|
console.log(chalk.green(` v ${block.models.length} model(s) saved to userSelected`));
|
|
1301
1520
|
for (const id of block.models) {
|
|
@@ -1389,6 +1608,10 @@ export async function run(name, args, isHelpRequest) {
|
|
|
1389
1608
|
const picked = await pickModels({ candidates, current });
|
|
1390
1609
|
if (picked.length === 0) {
|
|
1391
1610
|
const block = applyModels({ routerPath, models: [], source: 'live-pick' });
|
|
1611
|
+
// Clear Claude Code modelOverrides + modelPicker when the picker is
|
|
1612
|
+
// emptied so the session no longer claims to recognise removed IDs.
|
|
1613
|
+
applyModelOverrides({ pickedIds: [], liveIds: [] });
|
|
1614
|
+
applyModelPicker({ pickedIds: [], liveIds: [] });
|
|
1392
1615
|
if (wantJson) {
|
|
1393
1616
|
process.stdout.write(JSON.stringify({ applied: block, endpoint, endpointSource }, null, 2) + '\n');
|
|
1394
1617
|
} else {
|
|
@@ -1403,9 +1626,23 @@ export async function run(name, args, isHelpRequest) {
|
|
|
1403
1626
|
const profile = candidates.find((candidate) => candidate.id === id)?.profile;
|
|
1404
1627
|
if (profile) profiles[id] = profile;
|
|
1405
1628
|
}
|
|
1629
|
+
// F-191 / 10.19.2 — stale-ID detection: the picker shows candidates
|
|
1630
|
+
// filtered through the live gateway, but `--set` and the picker can both
|
|
1631
|
+
// accept IDs the gateway later rejects (e.g. `a/1`). Persist them as
|
|
1632
|
+
// `staleIds` for audit and skip them in the settings.json sync so Claude
|
|
1633
|
+
// Code still surfaces the unrecognized_model diagnostic.
|
|
1634
|
+
const liveIds = candidates.map((c) => c.id);
|
|
1635
|
+
const partition = partitionStalePicks({ liveIds, pickedIds: picked });
|
|
1406
1636
|
const block = applyModels({ routerPath, models: picked, tierHints, profiles, source: 'live-pick' });
|
|
1637
|
+
if (partition.staleIds.length > 0) {
|
|
1638
|
+
block.staleIds = partition.staleIds;
|
|
1639
|
+
}
|
|
1640
|
+
const sync = applyModelOverrides({ pickedIds: picked, liveIds });
|
|
1641
|
+
// Sync the /model picker contents (`modelPicker` setting) so the user's
|
|
1642
|
+
// picks drive the picker without relying on gateway discovery.
|
|
1643
|
+
const picker = applyModelPicker({ pickedIds: picked, profiles, liveIds });
|
|
1407
1644
|
if (wantJson) {
|
|
1408
|
-
process.stdout.write(JSON.stringify({ applied: block, endpoint, endpointSource }, null, 2) + '\n');
|
|
1645
|
+
process.stdout.write(JSON.stringify({ applied: block, endpoint, endpointSource, sync, picker }, null, 2) + '\n');
|
|
1409
1646
|
} else {
|
|
1410
1647
|
console.log(chalk.green(`\n v Saved ${block.models.length} model(s) to ${routerPath}:`));
|
|
1411
1648
|
console.log(chalk.dim(` Models.dev profiles: ${Object.keys(block.profiles || {}).length}/${block.models.length}`));
|
|
@@ -1413,6 +1650,12 @@ export async function run(name, args, isHelpRequest) {
|
|
|
1413
1650
|
const tier = block.tierHints[id] || defaultTierHint(id);
|
|
1414
1651
|
console.log(chalk.dim(` ${id} (${tier})`));
|
|
1415
1652
|
}
|
|
1653
|
+
if (sync.wrote && sync.skippedStale.length > 0) {
|
|
1654
|
+
console.log(chalk.yellow(` Settings sync skipped ${sync.skippedStale.length} stale id(s): ${sync.skippedStale.join(', ')}`));
|
|
1655
|
+
}
|
|
1656
|
+
if (picker.wrote) {
|
|
1657
|
+
console.log(chalk.dim(` /model picker populated with ${picker.entries.length} entr${picker.entries.length === 1 ? 'y' : 'ies'}`));
|
|
1658
|
+
}
|
|
1416
1659
|
}
|
|
1417
1660
|
return true;
|
|
1418
1661
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Launch Claude Code in this project on the premium tier (
|
|
2
|
+
description: Launch Claude Code in this project on the premium tier (qct/qwen3.8-max-preview). Prints the exact shell command and env vars to set.
|
|
3
3
|
allowed-tools: Bash
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# /use-premium — Run this session on
|
|
6
|
+
# /use-premium — Run this session on qct/qwen3.8-max-preview (premium)
|
|
7
7
|
|
|
8
|
-
The provider gateway you have configured exposes `
|
|
8
|
+
The provider gateway you have configured exposes `qct/qwen3.8-max-preview`
|
|
9
9
|
as the premium-tier reasoning model. Bizar is provider-agnostic — set
|
|
10
10
|
`BIZAR_MODEL_ROUTER_URL` (or `ANTHROPIC_BASE_URL`) to whatever gateway
|
|
11
11
|
URL your operator runs, then point the model at the premium tier. Premium
|
|
@@ -13,7 +13,7 @@ is expensive — use it intentionally, not by default.
|
|
|
13
13
|
|
|
14
14
|
## What it does
|
|
15
15
|
|
|
16
|
-
Sets `ANTHROPIC_MODEL=
|
|
16
|
+
Sets `ANTHROPIC_MODEL=qct/qwen3.8-max-preview` and forwards
|
|
17
17
|
`ANTHROPIC_BASE_URL` from your operator-configured gateway so the entire
|
|
18
18
|
session loop runs on the premium model. The model-router is the same
|
|
19
19
|
one your subagents (`@mike`, `@paul`, `@carl`) already hit.
|
|
@@ -24,7 +24,7 @@ one your subagents (`@mike`, `@paul`, `@carl`) already hit.
|
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
ANTHROPIC_BASE_URL="${BIZAR_MODEL_ROUTER_URL:-http://your-gateway/v1}" \
|
|
27
|
-
ANTHROPIC_MODEL=
|
|
27
|
+
ANTHROPIC_MODEL=qct/qwen3.8-max-preview \
|
|
28
28
|
claude
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -33,7 +33,7 @@ router as the model id):
|
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
ANTHROPIC_BASE_URL="${BIZAR_MODEL_ROUTER_URL:-http://your-gateway/v1}" \
|
|
36
|
-
claude --model
|
|
36
|
+
claude --model qct/qwen3.8-max-preview
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
### Make it the default for this project
|
|
@@ -44,7 +44,7 @@ Add to your local override `.claude/settings.local.json`:
|
|
|
44
44
|
{
|
|
45
45
|
"env": {
|
|
46
46
|
"ANTHROPIC_BASE_URL": "${BIZAR_MODEL_ROUTER_URL}",
|
|
47
|
-
"ANTHROPIC_MODEL": "
|
|
47
|
+
"ANTHROPIC_MODEL": "qct/qwen3.8-max-preview"
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
```
|
|
@@ -54,9 +54,9 @@ Then every Claude Code session in this directory opens on premium.
|
|
|
54
54
|
### Available premium-tier models on the local router
|
|
55
55
|
|
|
56
56
|
```
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
qct/qwen3.8-max-preview — strongest reasoning (orchestration, planning, debug)
|
|
58
|
+
codex/gpt-5.6-sol — high reasoning at lower cost (complex impl, review)
|
|
59
|
+
codex/gpt-5.6-luna — visually-oriented mid-tier (design)
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
See `.claude/model-router.json` for the full tier table.
|
|
@@ -70,9 +70,9 @@ See `.claude/model-router.json` for the full tier table.
|
|
|
70
70
|
|
|
71
71
|
## When NOT to use premium
|
|
72
72
|
|
|
73
|
-
- Routine edits, mechanical work, single-file fixes (use default `
|
|
73
|
+
- Routine edits, mechanical work, single-file fixes (use default `minimax/MiniMax-M3`).
|
|
74
74
|
- Research (always use `@greg` on default).
|
|
75
|
-
- Implementation at moderate complexity (use `
|
|
75
|
+
- Implementation at moderate complexity (use `minimax/MiniMax-M2.7` via `@todd`).
|
|
76
76
|
|
|
77
77
|
The harness's cost ceiling (`costCeilingPerSessionUsd: 5.0` in
|
|
78
78
|
`model-router.json`) will downgrade you automatically if the budget is
|
|
@@ -20,48 +20,51 @@
|
|
|
20
20
|
"tiers": {
|
|
21
21
|
"premium": {
|
|
22
22
|
"models": [
|
|
23
|
-
"
|
|
24
|
-
"
|
|
23
|
+
"qct/qwen3.8-max-preview",
|
|
24
|
+
"codex/gpt-5.6-sol",
|
|
25
|
+
"codex/gpt-5.6-luna"
|
|
25
26
|
],
|
|
26
27
|
"purpose": "Architecture, high-risk planning, adversarial verification, and last-resort debugging where mistakes are expensive.",
|
|
27
28
|
"effort": "high"
|
|
28
29
|
},
|
|
29
30
|
"high": {
|
|
30
31
|
"models": [
|
|
31
|
-
"
|
|
32
|
-
"
|
|
32
|
+
"codex/gpt-5.6-sol",
|
|
33
|
+
"qct/qwen3.8-max-preview"
|
|
33
34
|
],
|
|
34
35
|
"purpose": "Complex implementation, security review, and cross-cutting reasoning.",
|
|
35
36
|
"effort": "high"
|
|
36
37
|
},
|
|
37
38
|
"mid-design": {
|
|
38
39
|
"models": [
|
|
39
|
-
"
|
|
40
|
-
"
|
|
40
|
+
"codex/gpt-5.6-luna",
|
|
41
|
+
"minimax/MiniMax-M3"
|
|
41
42
|
],
|
|
42
43
|
"purpose": "UI/UX design and product-quality implementation where visual judgment matters.",
|
|
43
44
|
"effort": "medium"
|
|
44
45
|
},
|
|
45
46
|
"default": {
|
|
46
47
|
"models": [
|
|
47
|
-
"
|
|
48
|
-
"
|
|
48
|
+
"minimax/MiniMax-M3",
|
|
49
|
+
"minimax/MiniMax-M2.7"
|
|
49
50
|
],
|
|
50
51
|
"purpose": "Broad research, git operations, and ordinary engineering work.",
|
|
51
52
|
"effort": "medium"
|
|
52
53
|
},
|
|
53
54
|
"mid": {
|
|
54
55
|
"models": [
|
|
55
|
-
"
|
|
56
|
-
"
|
|
56
|
+
"minimax/MiniMax-M2.7",
|
|
57
|
+
"minimax/MiniMax-M2.7-highspeed",
|
|
58
|
+
"qct/deepseek-v4-pro",
|
|
59
|
+
"openrouter/nvidia/nemotron-3-ultra-550b-a55b:free"
|
|
57
60
|
],
|
|
58
61
|
"purpose": "Bounded technical analysis, code search, and moderate implementation.",
|
|
59
62
|
"effort": "medium"
|
|
60
63
|
},
|
|
61
64
|
"budget": {
|
|
62
65
|
"models": [
|
|
63
|
-
"
|
|
64
|
-
"
|
|
66
|
+
"glm/glm-5.3-flash",
|
|
67
|
+
"minimax/MiniMax-M2.7-highspeed"
|
|
65
68
|
],
|
|
66
69
|
"purpose": "Routine deterministic edits, targeted clarification, and scripted verification.",
|
|
67
70
|
"effort": "low"
|
package/package.json
CHANGED