theclawbay 0.3.78 → 0.3.79
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/dist/commands/setup.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export default class SetupCommand extends BaseCommand {
|
|
|
7
7
|
"device-name": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
8
|
clients: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
9
|
model: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
models: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
11
|
"claude-models": import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
12
|
"list-models": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
13
|
backup: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
package/dist/commands/setup.js
CHANGED
|
@@ -673,7 +673,66 @@ function powerShellQuote(value) {
|
|
|
673
673
|
return `'${value.replace(/'/g, "''")}'`;
|
|
674
674
|
}
|
|
675
675
|
function modelDisplayName(modelId) {
|
|
676
|
-
return MODEL_DISPLAY_NAMES[modelId] ?? modelId;
|
|
676
|
+
return MODEL_DISPLAY_NAMES[modelId] ?? prettyModelId(modelId);
|
|
677
|
+
}
|
|
678
|
+
function supportedModelProviderMap() {
|
|
679
|
+
return new Map((0, supported_models_1.getSupportedModels)().map((model) => [model.id, model.provider]));
|
|
680
|
+
}
|
|
681
|
+
function groupForModelId(modelId) {
|
|
682
|
+
const knownProvider = supportedModelProviderMap().get(modelId);
|
|
683
|
+
if (knownProvider === "openai") {
|
|
684
|
+
return { id: "openai", label: "OpenAI / Codex", icon: "◎", order: 0 };
|
|
685
|
+
}
|
|
686
|
+
if (knownProvider === "google") {
|
|
687
|
+
return { id: "google", label: "Google Gemini", icon: "◈", order: 1 };
|
|
688
|
+
}
|
|
689
|
+
if (knownProvider === "deepseek") {
|
|
690
|
+
return { id: "deepseek", label: "DeepSeek", icon: "◉", order: 2 };
|
|
691
|
+
}
|
|
692
|
+
if (modelId.startsWith("claude-")) {
|
|
693
|
+
return { id: "anthropic", label: "Anthropic Claude", icon: "✳", order: 3 };
|
|
694
|
+
}
|
|
695
|
+
if (modelId.startsWith("gemini-")) {
|
|
696
|
+
return { id: "google", label: "Google Gemini", icon: "◈", order: 1 };
|
|
697
|
+
}
|
|
698
|
+
if (modelId.startsWith("deepseek-")) {
|
|
699
|
+
return { id: "deepseek", label: "DeepSeek", icon: "◉", order: 2 };
|
|
700
|
+
}
|
|
701
|
+
if (modelId.startsWith("gpt-") || modelId.startsWith("codex-")) {
|
|
702
|
+
return { id: "openai", label: "OpenAI / Codex", icon: "◎", order: 0 };
|
|
703
|
+
}
|
|
704
|
+
return { id: "other", label: "Other models", icon: "•", order: 9 };
|
|
705
|
+
}
|
|
706
|
+
function groupModelOptions(models) {
|
|
707
|
+
return models.map((model) => ({
|
|
708
|
+
...model,
|
|
709
|
+
provider: groupForModelId(model.id),
|
|
710
|
+
}));
|
|
711
|
+
}
|
|
712
|
+
function groupModelSections(models) {
|
|
713
|
+
const sections = new Map();
|
|
714
|
+
for (const model of groupModelOptions(models)) {
|
|
715
|
+
const existing = sections.get(model.provider.id);
|
|
716
|
+
if (existing) {
|
|
717
|
+
existing.models.push(model);
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
sections.set(model.provider.id, { provider: model.provider, models: [model] });
|
|
721
|
+
}
|
|
722
|
+
return [...sections.values()]
|
|
723
|
+
.sort((left, right) => left.provider.order - right.provider.order || left.provider.label.localeCompare(right.provider.label))
|
|
724
|
+
.map((section) => ({
|
|
725
|
+
provider: section.provider,
|
|
726
|
+
models: [...section.models].sort((left, right) => left.name.localeCompare(right.name)),
|
|
727
|
+
}));
|
|
728
|
+
}
|
|
729
|
+
function prettyModelId(modelId) {
|
|
730
|
+
return modelId
|
|
731
|
+
.replace(/^claude-/, "Claude ")
|
|
732
|
+
.replace(/^gemini-/, "Gemini ")
|
|
733
|
+
.replace(/^deepseek-/, "DeepSeek ")
|
|
734
|
+
.replace(/-/g, " ")
|
|
735
|
+
.replace(/\b\w/g, (match) => match.toUpperCase());
|
|
677
736
|
}
|
|
678
737
|
function publicApiOriginForBackendUrl(backendUrl) {
|
|
679
738
|
const trimmed = trimTrailingSlash(backendUrl);
|
|
@@ -691,6 +750,39 @@ function publicApiOriginForBackendUrl(backendUrl) {
|
|
|
691
750
|
return trimmed;
|
|
692
751
|
}
|
|
693
752
|
}
|
|
753
|
+
function isPrivateHostname(hostname) {
|
|
754
|
+
const normalized = hostname.trim().toLowerCase();
|
|
755
|
+
if (!normalized)
|
|
756
|
+
return false;
|
|
757
|
+
if (normalized === "localhost" ||
|
|
758
|
+
normalized === "127.0.0.1" ||
|
|
759
|
+
normalized === "::1" ||
|
|
760
|
+
normalized === "[::1]" ||
|
|
761
|
+
normalized === "0.0.0.0") {
|
|
762
|
+
return true;
|
|
763
|
+
}
|
|
764
|
+
if (normalized.startsWith("10.") || normalized.startsWith("192.168.") || normalized.startsWith("127.")) {
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
const match = normalized.match(/^172\.(\d{1,3})\./);
|
|
768
|
+
if (!match)
|
|
769
|
+
return false;
|
|
770
|
+
const secondOctet = Number.parseInt(match[1] ?? "", 10);
|
|
771
|
+
return Number.isInteger(secondOctet) && secondOctet >= 16 && secondOctet <= 31;
|
|
772
|
+
}
|
|
773
|
+
function shouldPreferPublicSetupBackend(backendUrl) {
|
|
774
|
+
const trimmed = backendUrl?.trim();
|
|
775
|
+
if (!trimmed)
|
|
776
|
+
return false;
|
|
777
|
+
try {
|
|
778
|
+
const parsed = new URL(trimmed);
|
|
779
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
780
|
+
return hostname !== THECLAWBAY_CANONICAL_API_HOST && !THECLAWBAY_WEBSITE_HOSTS.has(hostname) && isPrivateHostname(hostname);
|
|
781
|
+
}
|
|
782
|
+
catch {
|
|
783
|
+
return false;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
694
786
|
function isTheClawBayChatgptBaseUrl(value) {
|
|
695
787
|
const trimmed = trimTrailingSlash(value.trim());
|
|
696
788
|
if (!trimmed)
|
|
@@ -1229,60 +1321,317 @@ async function resolveDeviceLabel(params) {
|
|
|
1229
1321
|
rl.close();
|
|
1230
1322
|
}
|
|
1231
1323
|
}
|
|
1324
|
+
async function promptForGroupedModelSelection(params) {
|
|
1325
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || params.models.length <= 1) {
|
|
1326
|
+
return uniqueStrings(params.defaultSelectedIds);
|
|
1327
|
+
}
|
|
1328
|
+
const sections = groupModelSections(params.models);
|
|
1329
|
+
const knownIds = new Set(params.models.map((model) => model.id));
|
|
1330
|
+
const selected = new Set(uniqueStrings(params.defaultSelectedIds).filter((modelId) => knownIds.has(modelId)));
|
|
1331
|
+
const rows = [];
|
|
1332
|
+
for (const [sectionIndex, section] of sections.entries()) {
|
|
1333
|
+
rows.push({ kind: "group", sectionIndex });
|
|
1334
|
+
for (const [modelIndex] of section.models.entries()) {
|
|
1335
|
+
rows.push({ kind: "model", sectionIndex, modelIndex });
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
rows.push({ kind: "apply" });
|
|
1339
|
+
const stdin = process.stdin;
|
|
1340
|
+
const stdout = process.stdout;
|
|
1341
|
+
const wasRaw = stdin.isRaw;
|
|
1342
|
+
const ansi = {
|
|
1343
|
+
reset: "\x1b[0m",
|
|
1344
|
+
bold: "\x1b[1m",
|
|
1345
|
+
dim: "\x1b[2m",
|
|
1346
|
+
inverse: "\x1b[7m",
|
|
1347
|
+
green: "\x1b[32m",
|
|
1348
|
+
yellow: "\x1b[33m",
|
|
1349
|
+
gray: "\x1b[90m",
|
|
1350
|
+
};
|
|
1351
|
+
const paint = (text, ...codes) => `${codes.join("")}${text}${ansi.reset}`;
|
|
1352
|
+
const clearScreen = () => {
|
|
1353
|
+
stdout.write("\x1b[2J\x1b[H");
|
|
1354
|
+
};
|
|
1355
|
+
const rowForModel = (sectionIndex, modelIndex) => rows.findIndex((row) => row.kind === "model" && row.sectionIndex === sectionIndex && row.modelIndex === modelIndex);
|
|
1356
|
+
let settled = false;
|
|
1357
|
+
let hint = "Use ↑/↓ to move. Press Enter or Space to toggle a model or a provider header.";
|
|
1358
|
+
let cursor = Math.max(0, rows.findIndex((row) => {
|
|
1359
|
+
if (row.kind !== "model")
|
|
1360
|
+
return false;
|
|
1361
|
+
const model = sections[row.sectionIndex]?.models[row.modelIndex];
|
|
1362
|
+
return model?.id === params.defaultSelectedIds[0];
|
|
1363
|
+
}));
|
|
1364
|
+
const groupState = (sectionIndex) => {
|
|
1365
|
+
const models = sections[sectionIndex]?.models ?? [];
|
|
1366
|
+
const selectedCount = models.filter((model) => selected.has(model.id)).length;
|
|
1367
|
+
if (selectedCount === 0)
|
|
1368
|
+
return "[ ]";
|
|
1369
|
+
if (selectedCount === models.length)
|
|
1370
|
+
return "[x]";
|
|
1371
|
+
return "[-]";
|
|
1372
|
+
};
|
|
1373
|
+
const setGroupSelection = (sectionIndex, nextValue) => {
|
|
1374
|
+
for (const model of sections[sectionIndex]?.models ?? []) {
|
|
1375
|
+
if (nextValue)
|
|
1376
|
+
selected.add(model.id);
|
|
1377
|
+
else
|
|
1378
|
+
selected.delete(model.id);
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
const toggleCurrent = () => {
|
|
1382
|
+
const current = rows[cursor];
|
|
1383
|
+
if (!current)
|
|
1384
|
+
return;
|
|
1385
|
+
if (current.kind === "apply") {
|
|
1386
|
+
finish();
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
if (current.kind === "group") {
|
|
1390
|
+
const state = groupState(current.sectionIndex);
|
|
1391
|
+
const nextValue = state !== "[x]";
|
|
1392
|
+
setGroupSelection(current.sectionIndex, nextValue);
|
|
1393
|
+
hint = `${sections[current.sectionIndex]?.provider.label ?? "Group"} ${nextValue ? "selected" : "cleared"}.`;
|
|
1394
|
+
render();
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
const model = sections[current.sectionIndex]?.models[current.modelIndex];
|
|
1398
|
+
if (!model)
|
|
1399
|
+
return;
|
|
1400
|
+
if (selected.has(model.id))
|
|
1401
|
+
selected.delete(model.id);
|
|
1402
|
+
else
|
|
1403
|
+
selected.add(model.id);
|
|
1404
|
+
hint = `${model.name} ${selected.has(model.id) ? "selected" : "cleared"}.`;
|
|
1405
|
+
render();
|
|
1406
|
+
};
|
|
1407
|
+
const finish = () => {
|
|
1408
|
+
if (settled)
|
|
1409
|
+
return;
|
|
1410
|
+
settled = true;
|
|
1411
|
+
stdin.off("keypress", onKeypress);
|
|
1412
|
+
if (stdin.isTTY)
|
|
1413
|
+
stdin.setRawMode(Boolean(wasRaw));
|
|
1414
|
+
stdout.write("\x1b[?25h");
|
|
1415
|
+
clearScreen();
|
|
1416
|
+
resolvePromise([...selected].filter((modelId) => knownIds.has(modelId)));
|
|
1417
|
+
};
|
|
1418
|
+
const render = () => {
|
|
1419
|
+
clearScreen();
|
|
1420
|
+
stdout.write(`${paint(params.title, ansi.bold)}\n`);
|
|
1421
|
+
stdout.write(`${paint("Each provider header toggles that provider on or off. Press A to toggle all models.", ansi.dim, ansi.gray)}\n`);
|
|
1422
|
+
stdout.write(`${paint("Move to Apply and press Enter when you're ready.", ansi.dim, ansi.gray)}\n\n`);
|
|
1423
|
+
for (const [sectionIndex, section] of sections.entries()) {
|
|
1424
|
+
const currentRow = rows[cursor];
|
|
1425
|
+
const groupCursor = currentRow?.kind === "group" && currentRow.sectionIndex === sectionIndex;
|
|
1426
|
+
const groupPointer = groupCursor ? paint("→", ansi.bold, ansi.yellow) : " ";
|
|
1427
|
+
const groupMark = groupState(sectionIndex);
|
|
1428
|
+
stdout.write(`${groupPointer} ${paint(groupMark, groupMark === "[x]" ? ansi.green : ansi.gray)} ${section.provider.icon} ${paint(`${section.provider.label} — Select all`, ansi.bold)}\n`);
|
|
1429
|
+
for (const [modelIndex, model] of section.models.entries()) {
|
|
1430
|
+
const activeRow = rows[cursor];
|
|
1431
|
+
const isActive = activeRow?.kind === "model" &&
|
|
1432
|
+
activeRow.sectionIndex === sectionIndex &&
|
|
1433
|
+
activeRow.modelIndex === modelIndex;
|
|
1434
|
+
const pointer = isActive ? paint("→", ansi.bold, ansi.yellow) : " ";
|
|
1435
|
+
const mark = selected.has(model.id) ? paint("[x]", ansi.green) : paint("[ ]", ansi.gray);
|
|
1436
|
+
stdout.write(`${pointer} ${mark} ${model.name} ${paint(`(${model.id})`, ansi.dim, ansi.gray)}\n`);
|
|
1437
|
+
}
|
|
1438
|
+
stdout.write("\n");
|
|
1439
|
+
}
|
|
1440
|
+
const applyActive = rows[cursor]?.kind === "apply";
|
|
1441
|
+
const applyStyled = applyActive
|
|
1442
|
+
? paint(` ${params.applyLabel} (${selected.size} selected) `, ansi.inverse, ansi.bold, ansi.green)
|
|
1443
|
+
: paint(`${params.applyLabel} (${selected.size} selected)`, ansi.bold, ansi.green);
|
|
1444
|
+
stdout.write(`${applyActive ? "→" : " "} ${applyStyled}\n`);
|
|
1445
|
+
stdout.write(`\n${paint(hint, ansi.dim, ansi.gray)}\n`);
|
|
1446
|
+
};
|
|
1447
|
+
const onKeypress = (_str, key) => {
|
|
1448
|
+
if (key.ctrl && key.name === "c") {
|
|
1449
|
+
stdout.write("\x1b[?25h");
|
|
1450
|
+
process.exit(130);
|
|
1451
|
+
}
|
|
1452
|
+
if (key.name === "up" || key.name === "k") {
|
|
1453
|
+
cursor = (cursor - 1 + rows.length) % rows.length;
|
|
1454
|
+
render();
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
if (key.name === "down" || key.name === "j") {
|
|
1458
|
+
cursor = (cursor + 1) % rows.length;
|
|
1459
|
+
render();
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
if (key.name === "space" || key.name === "return" || key.name === "enter") {
|
|
1463
|
+
toggleCurrent();
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
if (key.name === "a") {
|
|
1467
|
+
const nextValue = sections.some((section) => section.models.some((model) => !selected.has(model.id)));
|
|
1468
|
+
for (const [sectionIndex] of sections.entries())
|
|
1469
|
+
setGroupSelection(sectionIndex, nextValue);
|
|
1470
|
+
hint = `${nextValue ? "Selected" : "Cleared"} all available models.`;
|
|
1471
|
+
render();
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
if (key.name === "c") {
|
|
1475
|
+
finish();
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
let resolvePromise = () => { };
|
|
1479
|
+
const result = new Promise((resolve) => {
|
|
1480
|
+
resolvePromise = resolve;
|
|
1481
|
+
});
|
|
1482
|
+
(0, node_readline_1.emitKeypressEvents)(stdin);
|
|
1483
|
+
if (stdin.isTTY)
|
|
1484
|
+
stdin.setRawMode(true);
|
|
1485
|
+
stdout.write("\x1b[?25l");
|
|
1486
|
+
stdin.on("keypress", onKeypress);
|
|
1487
|
+
render();
|
|
1488
|
+
return result;
|
|
1489
|
+
}
|
|
1232
1490
|
async function promptForPrimaryModel(models, defaultModel) {
|
|
1233
1491
|
if (!process.stdin.isTTY || !process.stdout.isTTY || models.length <= 1) {
|
|
1234
1492
|
return defaultModel;
|
|
1235
1493
|
}
|
|
1236
|
-
const
|
|
1237
|
-
|
|
1238
|
-
|
|
1494
|
+
const sections = groupModelSections(models);
|
|
1495
|
+
const flattened = sections.flatMap((section) => section.models);
|
|
1496
|
+
const stdin = process.stdin;
|
|
1497
|
+
const stdout = process.stdout;
|
|
1498
|
+
const wasRaw = stdin.isRaw;
|
|
1499
|
+
const ansi = {
|
|
1500
|
+
reset: "\x1b[0m",
|
|
1501
|
+
bold: "\x1b[1m",
|
|
1502
|
+
dim: "\x1b[2m",
|
|
1503
|
+
green: "\x1b[32m",
|
|
1504
|
+
yellow: "\x1b[33m",
|
|
1505
|
+
gray: "\x1b[90m",
|
|
1506
|
+
};
|
|
1507
|
+
const paint = (text, ...codes) => `${codes.join("")}${text}${ansi.reset}`;
|
|
1508
|
+
const clearScreen = () => {
|
|
1509
|
+
stdout.write("\x1b[2J\x1b[H");
|
|
1510
|
+
};
|
|
1511
|
+
let cursor = Math.max(0, flattened.findIndex((model) => model.id === defaultModel));
|
|
1512
|
+
let settled = false;
|
|
1513
|
+
let resolvePromise = () => { };
|
|
1514
|
+
const finish = (modelId) => {
|
|
1515
|
+
if (settled)
|
|
1516
|
+
return;
|
|
1517
|
+
settled = true;
|
|
1518
|
+
stdin.off("keypress", onKeypress);
|
|
1519
|
+
if (stdin.isTTY)
|
|
1520
|
+
stdin.setRawMode(Boolean(wasRaw));
|
|
1521
|
+
stdout.write("\x1b[?25h");
|
|
1522
|
+
clearScreen();
|
|
1523
|
+
resolvePromise(modelId);
|
|
1524
|
+
};
|
|
1525
|
+
const render = () => {
|
|
1526
|
+
clearScreen();
|
|
1527
|
+
stdout.write(`${paint("Choose the default model for local setup", ansi.bold)}\n`);
|
|
1528
|
+
stdout.write(`${paint("Use ↑/↓ to move and Enter to select the default model.", ansi.dim, ansi.gray)}\n\n`);
|
|
1529
|
+
let index = 0;
|
|
1530
|
+
for (const section of sections) {
|
|
1531
|
+
stdout.write(`${paint(`${section.provider.icon} ${section.provider.label}`, ansi.bold)}\n`);
|
|
1532
|
+
for (const model of section.models) {
|
|
1533
|
+
const active = index === cursor;
|
|
1534
|
+
const pointer = active ? paint("→", ansi.bold, ansi.yellow) : " ";
|
|
1535
|
+
const defaultSuffix = model.id === defaultModel ? paint(" default", ansi.dim, ansi.gray) : "";
|
|
1536
|
+
stdout.write(`${pointer} ${model.name} ${paint(`(${model.id})`, ansi.dim, ansi.gray)}${defaultSuffix}\n`);
|
|
1537
|
+
index += 1;
|
|
1538
|
+
}
|
|
1539
|
+
stdout.write("\n");
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
const onKeypress = (_str, key) => {
|
|
1543
|
+
if (key.ctrl && key.name === "c") {
|
|
1544
|
+
stdout.write("\x1b[?25h");
|
|
1545
|
+
process.exit(130);
|
|
1546
|
+
}
|
|
1547
|
+
if (key.name === "up" || key.name === "k") {
|
|
1548
|
+
cursor = (cursor - 1 + flattened.length) % flattened.length;
|
|
1549
|
+
render();
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
if (key.name === "down" || key.name === "j") {
|
|
1553
|
+
cursor = (cursor + 1) % flattened.length;
|
|
1554
|
+
render();
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
if (key.name === "return" || key.name === "enter" || key.name === "space") {
|
|
1558
|
+
finish(flattened[cursor]?.id ?? defaultModel);
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
const result = new Promise((resolve) => {
|
|
1562
|
+
resolvePromise = resolve;
|
|
1239
1563
|
});
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
if (!trimmed)
|
|
1248
|
-
return defaultModel;
|
|
1249
|
-
const asNumber = Number.parseInt(trimmed, 10);
|
|
1250
|
-
if (Number.isInteger(asNumber) && asNumber >= 1 && asNumber <= models.length) {
|
|
1251
|
-
return models[asNumber - 1]?.id ?? defaultModel;
|
|
1252
|
-
}
|
|
1253
|
-
const directMatch = models.find((model) => model.id === trimmed);
|
|
1254
|
-
if (directMatch)
|
|
1255
|
-
return directMatch.id;
|
|
1256
|
-
throw new Error(`Unknown model selection "${trimmed}".`);
|
|
1257
|
-
}
|
|
1258
|
-
finally {
|
|
1259
|
-
rl.close();
|
|
1260
|
-
}
|
|
1564
|
+
(0, node_readline_1.emitKeypressEvents)(stdin);
|
|
1565
|
+
if (stdin.isTTY)
|
|
1566
|
+
stdin.setRawMode(true);
|
|
1567
|
+
stdout.write("\x1b[?25l");
|
|
1568
|
+
stdin.on("keypress", onKeypress);
|
|
1569
|
+
render();
|
|
1570
|
+
return result;
|
|
1261
1571
|
}
|
|
1262
1572
|
async function resolveConfiguredModelSelection(params) {
|
|
1263
1573
|
const requestedModel = params.requestedModel?.trim();
|
|
1574
|
+
const requestedModels = params.requestedModels
|
|
1575
|
+
?.split(",")
|
|
1576
|
+
.map((entry) => entry.trim())
|
|
1577
|
+
.filter(Boolean);
|
|
1264
1578
|
const availableIds = new Set(params.resolved.models.map((entry) => entry.id));
|
|
1265
|
-
if (requestedModel) {
|
|
1266
|
-
if (availableIds.size === 0 || availableIds.has(requestedModel)) {
|
|
1267
|
-
return {
|
|
1268
|
-
model: requestedModel,
|
|
1269
|
-
note: availableIds.size === 0 && params.resolved.failure
|
|
1270
|
-
? `Could not verify requested model ${requestedModel} against the live backend (${params.resolved.failure}).`
|
|
1271
|
-
: undefined,
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
1579
|
+
if (requestedModel && availableIds.size > 0 && !availableIds.has(requestedModel)) {
|
|
1274
1580
|
throw new Error(`Requested model "${requestedModel}" is not advertised by the live backend.`);
|
|
1275
1581
|
}
|
|
1276
|
-
if (
|
|
1277
|
-
|
|
1582
|
+
if (requestedModels?.length) {
|
|
1583
|
+
for (const modelId of requestedModels) {
|
|
1584
|
+
if (availableIds.size > 0 && !availableIds.has(modelId)) {
|
|
1585
|
+
throw new Error(`Requested model "${modelId}" is not advertised by the live backend.`);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
let selectedModels;
|
|
1590
|
+
if (requestedModels?.length) {
|
|
1591
|
+
selectedModels = params.resolved.models.filter((entry) => requestedModels.includes(entry.id));
|
|
1278
1592
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1593
|
+
else if (params.skipPrompt || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1594
|
+
selectedModels = params.resolved.models;
|
|
1595
|
+
}
|
|
1596
|
+
else if (params.resolved.models.length > 1) {
|
|
1597
|
+
const selectedIds = new Set(await promptForGroupedModelSelection({
|
|
1598
|
+
title: "Choose the OpenAI-compatible models to save into local tools",
|
|
1599
|
+
models: params.resolved.models,
|
|
1600
|
+
defaultSelectedIds: params.resolved.models.map((entry) => entry.id),
|
|
1601
|
+
applyLabel: "Apply model selection",
|
|
1602
|
+
}));
|
|
1603
|
+
selectedModels = params.resolved.models.filter((entry) => selectedIds.has(entry.id));
|
|
1604
|
+
}
|
|
1605
|
+
else {
|
|
1606
|
+
selectedModels = params.resolved.models;
|
|
1607
|
+
}
|
|
1608
|
+
const fallbackModels = selectedModels.length > 0 ? selectedModels : params.resolved.models;
|
|
1609
|
+
const selectedModelId = requestedModel && fallbackModels.some((entry) => entry.id === requestedModel)
|
|
1610
|
+
? requestedModel
|
|
1611
|
+
: params.skipPrompt || !process.stdin.isTTY || !process.stdout.isTTY
|
|
1612
|
+
? fallbackModels.some((entry) => entry.id === params.resolved.model)
|
|
1613
|
+
? params.resolved.model
|
|
1614
|
+
: fallbackModels[0]?.id ?? params.resolved.model
|
|
1615
|
+
: await promptForPrimaryModel(fallbackModels, fallbackModels.some((entry) => entry.id === params.resolved.model)
|
|
1616
|
+
? params.resolved.model
|
|
1617
|
+
: (fallbackModels[0]?.id ?? params.resolved.model));
|
|
1618
|
+
const noteParts = [];
|
|
1619
|
+
if (requestedModels?.length) {
|
|
1620
|
+
noteParts.push(`Configured ${requestedModels.length} OpenAI-compatible models.`);
|
|
1621
|
+
}
|
|
1622
|
+
else if (selectedModels.length > 0 && selectedModels.length < params.resolved.models.length) {
|
|
1623
|
+
noteParts.push(`Configured ${selectedModels.length} OpenAI-compatible models for local tools.`);
|
|
1624
|
+
}
|
|
1625
|
+
if (selectedModelId !== params.resolved.model) {
|
|
1626
|
+
noteParts.push(`Using ${selectedModelId} as the default configured model for local tools.`);
|
|
1627
|
+
}
|
|
1628
|
+
if (availableIds.size === 0 && requestedModel && params.resolved.failure) {
|
|
1629
|
+
noteParts.push(`Could not verify requested model ${requestedModel} against the live backend (${params.resolved.failure}).`);
|
|
1282
1630
|
}
|
|
1283
1631
|
return {
|
|
1284
|
-
model:
|
|
1285
|
-
|
|
1632
|
+
model: selectedModelId,
|
|
1633
|
+
models: fallbackModels,
|
|
1634
|
+
note: noteParts.join(" ").trim() || undefined,
|
|
1286
1635
|
};
|
|
1287
1636
|
}
|
|
1288
1637
|
function summarizeModelFetchFailure(detail) {
|
|
@@ -1597,6 +1946,12 @@ async function detectHermesClient() {
|
|
|
1597
1946
|
}
|
|
1598
1947
|
return false;
|
|
1599
1948
|
}
|
|
1949
|
+
function isOpenAiCompatibleSetupModelId(modelId) {
|
|
1950
|
+
const trimmed = modelId.trim();
|
|
1951
|
+
if (!trimmed)
|
|
1952
|
+
return false;
|
|
1953
|
+
return !trimmed.startsWith("claude-");
|
|
1954
|
+
}
|
|
1600
1955
|
function formatDotEnvValue(value) {
|
|
1601
1956
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1602
1957
|
}
|
|
@@ -1678,7 +2033,8 @@ async function writeHermesConfig(params) {
|
|
|
1678
2033
|
}
|
|
1679
2034
|
async function resolveModels(backendUrl, apiKey) {
|
|
1680
2035
|
const { ids, failure } = await fetchBackendModelIds(backendUrl, apiKey);
|
|
1681
|
-
const
|
|
2036
|
+
const filteredIds = (ids ?? []).filter(isOpenAiCompatibleSetupModelId);
|
|
2037
|
+
const available = new Set(filteredIds);
|
|
1682
2038
|
let selected = DEFAULT_CODEX_MODEL;
|
|
1683
2039
|
let note;
|
|
1684
2040
|
const authRejected = isCredentialRejectedFailure(failure);
|
|
@@ -1688,29 +2044,29 @@ async function resolveModels(backendUrl, apiKey) {
|
|
|
1688
2044
|
break;
|
|
1689
2045
|
}
|
|
1690
2046
|
}
|
|
1691
|
-
if (
|
|
1692
|
-
selected =
|
|
2047
|
+
if (filteredIds.length && !available.has(selected)) {
|
|
2048
|
+
selected = filteredIds[0] ?? DEFAULT_CODEX_MODEL;
|
|
1693
2049
|
note = "No preferred Codex model advertised by backend; selected first available model.";
|
|
1694
2050
|
}
|
|
1695
|
-
else if (!
|
|
2051
|
+
else if (!filteredIds.length) {
|
|
1696
2052
|
note = failure
|
|
1697
2053
|
? `Unable to query backend model list (${failure}); defaulted to ${DEFAULT_CODEX_MODEL}.`
|
|
1698
2054
|
: `Unable to query backend model list; defaulted to ${DEFAULT_CODEX_MODEL}.`;
|
|
1699
2055
|
}
|
|
1700
2056
|
const unique = [];
|
|
1701
2057
|
const pushUnique = (modelId) => {
|
|
1702
|
-
if (!modelId || unique.includes(modelId))
|
|
2058
|
+
if (!modelId || !isOpenAiCompatibleSetupModelId(modelId) || unique.includes(modelId))
|
|
1703
2059
|
return;
|
|
1704
2060
|
unique.push(modelId);
|
|
1705
2061
|
};
|
|
1706
2062
|
pushUnique(selected);
|
|
1707
2063
|
for (const modelId of DEFAULT_MODELS)
|
|
1708
2064
|
pushUnique(modelId);
|
|
1709
|
-
for (const modelId of
|
|
2065
|
+
for (const modelId of filteredIds)
|
|
1710
2066
|
pushUnique(modelId);
|
|
1711
2067
|
return {
|
|
1712
2068
|
model: selected,
|
|
1713
|
-
models: unique.map((modelId) => ({ id: modelId, name: modelDisplayName(modelId) })),
|
|
2069
|
+
models: unique.map((modelId) => ({ id: modelId, name: modelDisplayName(modelId) || prettyModelId(modelId) })),
|
|
1714
2070
|
note,
|
|
1715
2071
|
failure,
|
|
1716
2072
|
authRejected,
|
|
@@ -1735,15 +2091,31 @@ async function resolveClaudeAccess(backendUrl, apiKey) {
|
|
|
1735
2091
|
authRejected,
|
|
1736
2092
|
};
|
|
1737
2093
|
}
|
|
1738
|
-
function resolveConfiguredClaudeModels(params) {
|
|
2094
|
+
async function resolveConfiguredClaudeModels(params) {
|
|
1739
2095
|
const requestedModels = params.requestedModels
|
|
1740
2096
|
?.split(",")
|
|
1741
2097
|
.map((entry) => entry.trim())
|
|
1742
2098
|
.filter(Boolean);
|
|
1743
2099
|
if (!requestedModels?.length || !params.access.enabled) {
|
|
2100
|
+
const selectedModels = !requestedModels?.length &&
|
|
2101
|
+
params.access.enabled &&
|
|
2102
|
+
!params.skipPrompt &&
|
|
2103
|
+
process.stdin.isTTY &&
|
|
2104
|
+
process.stdout.isTTY &&
|
|
2105
|
+
params.access.models.length > 1
|
|
2106
|
+
? await promptForGroupedModelSelection({
|
|
2107
|
+
title: "Choose the Claude models to save into Claude-compatible tools",
|
|
2108
|
+
models: params.access.models.map((modelId) => ({
|
|
2109
|
+
id: modelId,
|
|
2110
|
+
name: prettyModelId(modelId),
|
|
2111
|
+
})),
|
|
2112
|
+
defaultSelectedIds: params.access.models,
|
|
2113
|
+
applyLabel: "Apply Claude selection",
|
|
2114
|
+
})
|
|
2115
|
+
: params.access.models;
|
|
1744
2116
|
return {
|
|
1745
2117
|
...params.access,
|
|
1746
|
-
selectedModels
|
|
2118
|
+
selectedModels,
|
|
1747
2119
|
};
|
|
1748
2120
|
}
|
|
1749
2121
|
const available = new Set(params.access.models);
|
|
@@ -3073,13 +3445,25 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3073
3445
|
let deviceLabel = managedAuthType === "device-session"
|
|
3074
3446
|
? managed?.deviceLabel ?? null
|
|
3075
3447
|
: null;
|
|
3448
|
+
const inferredBackend = explicitApiKey || managedAuthType === "api-key"
|
|
3449
|
+
? (0, api_key_1.tryInferBackendUrlFromApiKey)(explicitApiKey || managedCredential)
|
|
3450
|
+
: null;
|
|
3451
|
+
const managedBackendUrl = managed?.backendUrl ?? null;
|
|
3076
3452
|
const backendRaw = flags.backend ??
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
: null) ??
|
|
3080
|
-
managed?.backendUrl ??
|
|
3453
|
+
inferredBackend ??
|
|
3454
|
+
(shouldPreferPublicSetupBackend(managedBackendUrl) ? DEFAULT_BACKEND_URL : managedBackendUrl) ??
|
|
3081
3455
|
DEFAULT_BACKEND_URL;
|
|
3082
3456
|
const backendUrl = normalizeUrl(backendRaw, "--backend");
|
|
3457
|
+
const reusingLocalManagedCredential = !explicitApiKey &&
|
|
3458
|
+
!flags.backend &&
|
|
3459
|
+
shouldPreferPublicSetupBackend(managedBackendUrl) &&
|
|
3460
|
+
authCredential.length > 0;
|
|
3461
|
+
if (reusingLocalManagedCredential) {
|
|
3462
|
+
authType = "device-session";
|
|
3463
|
+
authCredential = "";
|
|
3464
|
+
deviceSessionId = null;
|
|
3465
|
+
deviceLabel = null;
|
|
3466
|
+
}
|
|
3083
3467
|
const [codexDetected, claudeDetected, continueDetected, clineDetected, gsdDetected, openCodeDetected, kiloDetected, rooDetected, traeDetected, aiderDetected, zoDetected, hermesDetected] = await Promise.all([
|
|
3084
3468
|
detectCodexClient(),
|
|
3085
3469
|
detectClaudeClient(),
|
|
@@ -3289,14 +3673,17 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3289
3673
|
if (resolved.authRejected) {
|
|
3290
3674
|
throw new Error(describeCredentialRejection(authType, resolved.failure));
|
|
3291
3675
|
}
|
|
3676
|
+
progress.stop();
|
|
3292
3677
|
const configuredModel = await resolveConfiguredModelSelection({
|
|
3293
3678
|
resolved,
|
|
3294
3679
|
requestedModel: flags.model,
|
|
3680
|
+
requestedModels: flags.models,
|
|
3295
3681
|
skipPrompt: flags.yes,
|
|
3296
3682
|
});
|
|
3297
3683
|
resolved = {
|
|
3298
3684
|
...resolved,
|
|
3299
3685
|
model: configuredModel.model,
|
|
3686
|
+
models: configuredModel.models,
|
|
3300
3687
|
note: [resolved.note, configuredModel.note].filter(Boolean).join(" ").trim() || undefined,
|
|
3301
3688
|
};
|
|
3302
3689
|
}
|
|
@@ -3305,23 +3692,28 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
3305
3692
|
if (!resolved?.authRejected && claudeAccess.authRejected) {
|
|
3306
3693
|
throw new Error(describeCredentialRejection(authType, claudeAccess.failure));
|
|
3307
3694
|
}
|
|
3308
|
-
|
|
3695
|
+
progress.stop();
|
|
3696
|
+
claudeAccess = await resolveConfiguredClaudeModels({
|
|
3309
3697
|
access: claudeAccess,
|
|
3310
3698
|
requestedModels: flags["claude-models"],
|
|
3699
|
+
skipPrompt: flags.yes,
|
|
3311
3700
|
});
|
|
3312
3701
|
if (flags["list-models"]) {
|
|
3313
3702
|
if (resolved) {
|
|
3314
3703
|
this.log("\nOpenAI-compatible models:");
|
|
3315
|
-
for (const
|
|
3316
|
-
|
|
3317
|
-
|
|
3704
|
+
for (const section of groupModelSections(resolved.models)) {
|
|
3705
|
+
this.log(`${section.provider.icon} ${section.provider.label}`);
|
|
3706
|
+
for (const model of section.models) {
|
|
3707
|
+
const marker = model.id === resolved.model ? " (default)" : "";
|
|
3708
|
+
this.log(` - ${model.id}${marker}`);
|
|
3709
|
+
}
|
|
3318
3710
|
}
|
|
3319
3711
|
}
|
|
3320
3712
|
if (claudeAccess.models.length > 0) {
|
|
3321
3713
|
this.log("\nClaude models:");
|
|
3322
3714
|
for (const modelId of claudeAccess.models) {
|
|
3323
3715
|
const marker = claudeAccess.selectedModels?.includes(modelId) ? " (selected)" : "";
|
|
3324
|
-
this.log(
|
|
3716
|
+
this.log(` - ${modelId}${marker}`);
|
|
3325
3717
|
}
|
|
3326
3718
|
}
|
|
3327
3719
|
}
|
|
@@ -3849,6 +4241,10 @@ SetupCommand.flags = {
|
|
|
3849
4241
|
required: false,
|
|
3850
4242
|
description: "Default OpenAI-compatible model to configure for local tools",
|
|
3851
4243
|
}),
|
|
4244
|
+
models: core_1.Flags.string({
|
|
4245
|
+
required: false,
|
|
4246
|
+
description: "Comma-separated OpenAI-compatible model ids to save into local tools",
|
|
4247
|
+
}),
|
|
3852
4248
|
"claude-models": core_1.Flags.string({
|
|
3853
4249
|
required: false,
|
|
3854
4250
|
description: "Comma-separated Claude model ids to configure for Claude-compatible tools",
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export type SupportedModelTone = "coral" | "sea" | "ink";
|
|
2
|
+
export type SupportedModelProvider = "openai" | "google" | "deepseek";
|
|
2
3
|
export type SupportedModelConfig = {
|
|
3
4
|
id: string;
|
|
4
5
|
label: string;
|
|
5
6
|
note: string;
|
|
6
7
|
tone: SupportedModelTone;
|
|
8
|
+
provider: SupportedModelProvider;
|
|
7
9
|
inputPer1M: number;
|
|
8
10
|
cachedInputPer1M: number;
|
|
9
11
|
outputPer1M: number;
|
|
@@ -25,6 +25,7 @@ function parseSupportedModels(raw) {
|
|
|
25
25
|
const label = typeof record.label === "string" ? record.label.trim() : "";
|
|
26
26
|
const note = typeof record.note === "string" ? record.note.trim() : "";
|
|
27
27
|
const tone = record.tone;
|
|
28
|
+
const provider = record.provider;
|
|
28
29
|
const inputPer1M = typeof record.inputPer1M === "number" ? record.inputPer1M : NaN;
|
|
29
30
|
const cachedInputPer1M = typeof record.cachedInputPer1M === "number" ? record.cachedInputPer1M : NaN;
|
|
30
31
|
const outputPer1M = typeof record.outputPer1M === "number" ? record.outputPer1M : NaN;
|
|
@@ -37,6 +38,9 @@ function parseSupportedModels(raw) {
|
|
|
37
38
|
if (tone !== "coral" && tone !== "sea" && tone !== "ink") {
|
|
38
39
|
throw new Error(`unsupported tone for model ${id}`);
|
|
39
40
|
}
|
|
41
|
+
if (provider !== "openai" && provider !== "google" && provider !== "deepseek") {
|
|
42
|
+
throw new Error(`unsupported provider for model ${id}`);
|
|
43
|
+
}
|
|
40
44
|
if (!Number.isFinite(inputPer1M) || !Number.isFinite(cachedInputPer1M) || !Number.isFinite(outputPer1M)) {
|
|
41
45
|
throw new Error(`unsupported pricing fields for model ${id}`);
|
|
42
46
|
}
|
|
@@ -45,6 +49,7 @@ function parseSupportedModels(raw) {
|
|
|
45
49
|
label,
|
|
46
50
|
note,
|
|
47
51
|
tone,
|
|
52
|
+
provider,
|
|
48
53
|
inputPer1M,
|
|
49
54
|
cachedInputPer1M,
|
|
50
55
|
outputPer1M,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "theclawbay",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.79",
|
|
4
4
|
"description": "CLI for connecting Codex, Hermes Agent, Gemini-compatible apps, Continue, Cline, GSD, OpenClaw, OpenCode, Kilo, Roo Code, Aider, experimental Trae, and experimental Zo to The Claw Bay.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"label": "GPT-5.5",
|
|
5
5
|
"note": "Frontier model for complex coding, research, and real-world work.",
|
|
6
6
|
"tone": "coral",
|
|
7
|
+
"provider": "openai",
|
|
7
8
|
"inputPer1M": 5.0,
|
|
8
9
|
"cachedInputPer1M": 0.5,
|
|
9
10
|
"outputPer1M": 30.0
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
"label": "GPT-5.4",
|
|
14
15
|
"note": "Frontier coding model with the widest headroom.",
|
|
15
16
|
"tone": "coral",
|
|
17
|
+
"provider": "openai",
|
|
16
18
|
"inputPer1M": 2.5,
|
|
17
19
|
"cachedInputPer1M": 0.25,
|
|
18
20
|
"outputPer1M": 15.0
|
|
@@ -22,6 +24,7 @@
|
|
|
22
24
|
"label": "GPT-5.4 Mini",
|
|
23
25
|
"note": "Smaller GPT-5.4 variant for quick iterations.",
|
|
24
26
|
"tone": "sea",
|
|
27
|
+
"provider": "openai",
|
|
25
28
|
"inputPer1M": 1.25,
|
|
26
29
|
"cachedInputPer1M": 0.125,
|
|
27
30
|
"outputPer1M": 10.0
|
|
@@ -31,42 +34,17 @@
|
|
|
31
34
|
"label": "GPT Image 2",
|
|
32
35
|
"note": "OpenAI-compatible image generation endpoint backed by codex-lb.",
|
|
33
36
|
"tone": "coral",
|
|
37
|
+
"provider": "openai",
|
|
34
38
|
"inputPer1M": 5.0,
|
|
35
39
|
"cachedInputPer1M": 2.0,
|
|
36
40
|
"outputPer1M": 30.0
|
|
37
41
|
},
|
|
38
|
-
{
|
|
39
|
-
"id": "gpt-image-1.5",
|
|
40
|
-
"label": "GPT Image 1.5",
|
|
41
|
-
"note": "Native image generation model for direct image outputs.",
|
|
42
|
-
"tone": "coral",
|
|
43
|
-
"inputPer1M": 8.0,
|
|
44
|
-
"cachedInputPer1M": 2.0,
|
|
45
|
-
"outputPer1M": 32.0
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"id": "gpt-5.1-codex-max",
|
|
49
|
-
"label": "GPT-5.1 Codex Max",
|
|
50
|
-
"note": "Higher-throughput option for longer coding sessions.",
|
|
51
|
-
"tone": "coral",
|
|
52
|
-
"inputPer1M": 1.25,
|
|
53
|
-
"cachedInputPer1M": 0.125,
|
|
54
|
-
"outputPer1M": 10.0
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"id": "gpt-5.1-codex-mini",
|
|
58
|
-
"label": "GPT-5.1 Codex Mini",
|
|
59
|
-
"note": "Lower-cost Codex path for quick iterations.",
|
|
60
|
-
"tone": "sea",
|
|
61
|
-
"inputPer1M": 0.25,
|
|
62
|
-
"cachedInputPer1M": 0.025,
|
|
63
|
-
"outputPer1M": 2.0
|
|
64
|
-
},
|
|
65
42
|
{
|
|
66
43
|
"id": "gemini-2.5-pro",
|
|
67
44
|
"label": "Gemini 2.5 Pro",
|
|
68
45
|
"note": "Google's flagship coding and reasoning model through CliRelay.",
|
|
69
46
|
"tone": "coral",
|
|
47
|
+
"provider": "google",
|
|
70
48
|
"inputPer1M": 1.25,
|
|
71
49
|
"cachedInputPer1M": 0.125,
|
|
72
50
|
"outputPer1M": 10.0
|
|
@@ -76,6 +54,7 @@
|
|
|
76
54
|
"label": "Gemini 2.5 Flash",
|
|
77
55
|
"note": "Balanced Gemini path for fast multimodal and agentic work.",
|
|
78
56
|
"tone": "ink",
|
|
57
|
+
"provider": "google",
|
|
79
58
|
"inputPer1M": 0.3,
|
|
80
59
|
"cachedInputPer1M": 0.03,
|
|
81
60
|
"outputPer1M": 2.5
|
|
@@ -85,6 +64,7 @@
|
|
|
85
64
|
"label": "Gemini 2.5 Flash Lite",
|
|
86
65
|
"note": "Lowest-cost Gemini path for lightweight coding and tooling loops.",
|
|
87
66
|
"tone": "sea",
|
|
67
|
+
"provider": "google",
|
|
88
68
|
"inputPer1M": 0.1,
|
|
89
69
|
"cachedInputPer1M": 0.01,
|
|
90
70
|
"outputPer1M": 0.4
|
|
@@ -94,6 +74,7 @@
|
|
|
94
74
|
"label": "Gemini 3 Pro Preview",
|
|
95
75
|
"note": "Preview Gemini model for heavier multimodal and coding sessions.",
|
|
96
76
|
"tone": "coral",
|
|
77
|
+
"provider": "google",
|
|
97
78
|
"inputPer1M": 2.0,
|
|
98
79
|
"cachedInputPer1M": 0.2,
|
|
99
80
|
"outputPer1M": 12.0
|
|
@@ -103,6 +84,7 @@
|
|
|
103
84
|
"label": "Gemini 3.1 Pro Preview",
|
|
104
85
|
"note": "Newest Gemini Pro preview with stronger agentic and coding behavior.",
|
|
105
86
|
"tone": "coral",
|
|
87
|
+
"provider": "google",
|
|
106
88
|
"inputPer1M": 2.0,
|
|
107
89
|
"cachedInputPer1M": 0.2,
|
|
108
90
|
"outputPer1M": 12.0
|
|
@@ -112,6 +94,7 @@
|
|
|
112
94
|
"label": "Gemini 3 Flash Preview",
|
|
113
95
|
"note": "Faster Gemini preview tuned for responsive high-volume tasks.",
|
|
114
96
|
"tone": "ink",
|
|
97
|
+
"provider": "google",
|
|
115
98
|
"inputPer1M": 0.5,
|
|
116
99
|
"cachedInputPer1M": 0.05,
|
|
117
100
|
"outputPer1M": 3.0
|
|
@@ -121,6 +104,7 @@
|
|
|
121
104
|
"label": "DeepSeek V4 Flash",
|
|
122
105
|
"note": "Lowest-cost DeepSeek path with 1M context through CliRelay.",
|
|
123
106
|
"tone": "sea",
|
|
107
|
+
"provider": "deepseek",
|
|
124
108
|
"inputPer1M": 0.14,
|
|
125
109
|
"cachedInputPer1M": 0.0028,
|
|
126
110
|
"outputPer1M": 0.28,
|
|
@@ -131,6 +115,7 @@
|
|
|
131
115
|
"label": "DeepSeek V4 Pro",
|
|
132
116
|
"note": "Flagship DeepSeek reasoning path with 1M context through CliRelay.",
|
|
133
117
|
"tone": "coral",
|
|
118
|
+
"provider": "deepseek",
|
|
134
119
|
"inputPer1M": 0.435,
|
|
135
120
|
"cachedInputPer1M": 0.003625,
|
|
136
121
|
"outputPer1M": 0.87,
|