@polderlabs/bizar 10.19.0 → 10.19.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/cli/bin.mjs +3 -2
- package/cli/commands/models.mjs +137 -5
- package/config/claude/commands/use-premium.md +12 -12
- package/config/claude/model-router.json +15 -12
- package/package.json +1 -1
- package/packages/sdk/dist/version.d.ts +1 -1
- package/packages/sdk/dist/version.js +1 -1
- package/packages/sdk/package.json +1 -1
package/cli/bin.mjs
CHANGED
|
@@ -192,6 +192,9 @@ async function main() {
|
|
|
192
192
|
// Pass --help to the command. Commands dispatched through util.mjs
|
|
193
193
|
// (audit, init, export, doctor, backup, restore, etc.) don't have
|
|
194
194
|
// their own cli/commands/<name>.mjs — they all live in util.mjs.
|
|
195
|
+
// Direct command modules (bench, release-provenance, verify-release,
|
|
196
|
+
// spec-list, …) have their own --help handling and accept
|
|
197
|
+
// run(cmdArgs); they fall through to the switch below.
|
|
195
198
|
const UTIL_COMMANDS = new Set([
|
|
196
199
|
'audit', 'init', 'export', 'test-gate',
|
|
197
200
|
'doctor', 'repair', 'heads-up', 'backup', 'restore',
|
|
@@ -208,8 +211,6 @@ async function main() {
|
|
|
208
211
|
const { runMigrate } = await import('./migrate.mjs');
|
|
209
212
|
await runMigrate(['--help']);
|
|
210
213
|
return;
|
|
211
|
-
} else {
|
|
212
|
-
mod = await importCommand(cmd);
|
|
213
214
|
}
|
|
214
215
|
if (mod && typeof mod.run === 'function') {
|
|
215
216
|
await mod.run(cmd, cmdArgs, true);
|
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,102 @@ 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
|
+
|
|
486
595
|
// ── F-190 / IMP-017 refresh + operator-override preservation ────────────────
|
|
487
596
|
|
|
488
597
|
/**
|
|
@@ -1294,8 +1403,14 @@ export async function run(name, args, isHelpRequest) {
|
|
|
1294
1403
|
process.exit(2);
|
|
1295
1404
|
}
|
|
1296
1405
|
const block = applyModels({ routerPath, models: ids, source: 'cli-set' });
|
|
1406
|
+
// Sync to Claude Code's settings.json — picks survive stale-ID
|
|
1407
|
+
// filtering only when an empty liveIds array disables it (no
|
|
1408
|
+
// candidates fetched for `--set`). The orchestrator will surface
|
|
1409
|
+
// `[claude-code:unrecognized_model]` for any ID the gateway later
|
|
1410
|
+
// rejects; the operator can re-run `bizar models` to drop them.
|
|
1411
|
+
const sync = applyModelOverrides({ pickedIds: ids, liveIds: [] });
|
|
1297
1412
|
if (wantJson) {
|
|
1298
|
-
process.stdout.write(JSON.stringify({ applied: block }, null, 2) + '\n');
|
|
1413
|
+
process.stdout.write(JSON.stringify({ applied: block, sync }, null, 2) + '\n');
|
|
1299
1414
|
} else {
|
|
1300
1415
|
console.log(chalk.green(` v ${block.models.length} model(s) saved to userSelected`));
|
|
1301
1416
|
for (const id of block.models) {
|
|
@@ -1389,6 +1504,9 @@ export async function run(name, args, isHelpRequest) {
|
|
|
1389
1504
|
const picked = await pickModels({ candidates, current });
|
|
1390
1505
|
if (picked.length === 0) {
|
|
1391
1506
|
const block = applyModels({ routerPath, models: [], source: 'live-pick' });
|
|
1507
|
+
// Clear Claude Code modelOverrides when the picker is emptied so the
|
|
1508
|
+
// session no longer claims to recognise removed IDs.
|
|
1509
|
+
applyModelOverrides({ pickedIds: [], liveIds: [] });
|
|
1392
1510
|
if (wantJson) {
|
|
1393
1511
|
process.stdout.write(JSON.stringify({ applied: block, endpoint, endpointSource }, null, 2) + '\n');
|
|
1394
1512
|
} else {
|
|
@@ -1403,9 +1521,20 @@ export async function run(name, args, isHelpRequest) {
|
|
|
1403
1521
|
const profile = candidates.find((candidate) => candidate.id === id)?.profile;
|
|
1404
1522
|
if (profile) profiles[id] = profile;
|
|
1405
1523
|
}
|
|
1524
|
+
// F-191 / 10.19.2 — stale-ID detection: the picker shows candidates
|
|
1525
|
+
// filtered through the live gateway, but `--set` and the picker can both
|
|
1526
|
+
// accept IDs the gateway later rejects (e.g. `a/1`). Persist them as
|
|
1527
|
+
// `staleIds` for audit and skip them in the settings.json sync so Claude
|
|
1528
|
+
// Code still surfaces the unrecognized_model diagnostic.
|
|
1529
|
+
const liveIds = candidates.map((c) => c.id);
|
|
1530
|
+
const partition = partitionStalePicks({ liveIds, pickedIds: picked });
|
|
1406
1531
|
const block = applyModels({ routerPath, models: picked, tierHints, profiles, source: 'live-pick' });
|
|
1532
|
+
if (partition.staleIds.length > 0) {
|
|
1533
|
+
block.staleIds = partition.staleIds;
|
|
1534
|
+
}
|
|
1535
|
+
const sync = applyModelOverrides({ pickedIds: picked, liveIds });
|
|
1407
1536
|
if (wantJson) {
|
|
1408
|
-
process.stdout.write(JSON.stringify({ applied: block, endpoint, endpointSource }, null, 2) + '\n');
|
|
1537
|
+
process.stdout.write(JSON.stringify({ applied: block, endpoint, endpointSource, sync }, null, 2) + '\n');
|
|
1409
1538
|
} else {
|
|
1410
1539
|
console.log(chalk.green(`\n v Saved ${block.models.length} model(s) to ${routerPath}:`));
|
|
1411
1540
|
console.log(chalk.dim(` Models.dev profiles: ${Object.keys(block.profiles || {}).length}/${block.models.length}`));
|
|
@@ -1413,6 +1542,9 @@ export async function run(name, args, isHelpRequest) {
|
|
|
1413
1542
|
const tier = block.tierHints[id] || defaultTierHint(id);
|
|
1414
1543
|
console.log(chalk.dim(` ${id} (${tier})`));
|
|
1415
1544
|
}
|
|
1545
|
+
if (sync.wrote && sync.skippedStale.length > 0) {
|
|
1546
|
+
console.log(chalk.yellow(` Settings sync skipped ${sync.skippedStale.length} stale id(s): ${sync.skippedStale.join(', ')}`));
|
|
1547
|
+
}
|
|
1416
1548
|
}
|
|
1417
1549
|
return true;
|
|
1418
1550
|
}
|
|
@@ -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