theclawbay 0.3.31 → 0.3.33
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/README.md +1 -0
- package/dist/commands/logout.js +77 -41
- package/dist/commands/setup.js +161 -70
- package/dist/lib/codex-model-cache-migration.js +174 -85
- package/package.json +1 -1
- package/theclawbay-supported-models.json +9 -0
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ theclawbay setup --api-key <apiKey>
|
|
|
18
18
|
|
|
19
19
|
In an interactive terminal, setup shows one picker for Codex, Continue, Cline, OpenClaw, OpenCode, Kilo, Roo Code, Aider, and Windows Trae.
|
|
20
20
|
Each row includes a terminal-friendly icon plus the tool's official site, and you can toggle items with arrow keys plus `Enter` or by pressing their number.
|
|
21
|
+
Re-running setup also migrates legacy OpenCode and Kilo patches to the current reasoning-capable provider path and refreshes OpenClaw model metadata.
|
|
21
22
|
|
|
22
23
|
## Optional
|
|
23
24
|
|
package/dist/commands/logout.js
CHANGED
|
@@ -13,6 +13,7 @@ const codex_auth_seeding_1 = require("../lib/codex-auth-seeding");
|
|
|
13
13
|
const codex_history_migration_1 = require("../lib/codex-history-migration");
|
|
14
14
|
const codex_model_cache_migration_1 = require("../lib/codex-model-cache-migration");
|
|
15
15
|
const paths_1 = require("../lib/config/paths");
|
|
16
|
+
const supported_models_1 = require("../lib/supported-models");
|
|
16
17
|
const OPENAI_PROVIDER_ID = "openai";
|
|
17
18
|
const DEFAULT_PROVIDER_ID = "theclawbay";
|
|
18
19
|
const WAN_PROVIDER_ID = "theclawbay-wan";
|
|
@@ -29,6 +30,8 @@ const CLINE_GLOBAL_STATE_PATH = node_path_1.default.join(node_os_1.default.homed
|
|
|
29
30
|
const CLINE_SECRETS_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "secrets.json");
|
|
30
31
|
const AIDER_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".aider.conf.yml");
|
|
31
32
|
const CLINE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "cline.restore.json");
|
|
33
|
+
const OPENCODE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "opencode.restore.json");
|
|
34
|
+
const KILO_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "kilo.restore.json");
|
|
32
35
|
const ROO_SETTINGS_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "roo-settings.restore.json");
|
|
33
36
|
const ROO_IMPORT_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "roo-code-settings.json");
|
|
34
37
|
const MIGRATION_STATE_FILE = node_path_1.default.join(paths_1.codexDir, "theclawbay.migration.json");
|
|
@@ -39,6 +42,7 @@ const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set([
|
|
|
39
42
|
]);
|
|
40
43
|
const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = [DEFAULT_PROVIDER_ID, WAN_PROVIDER_ID];
|
|
41
44
|
const THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
|
|
45
|
+
const SUPPORTED_MODEL_IDS = new Set((0, supported_models_1.getSupportedModelIds)());
|
|
42
46
|
const CONTINUE_MODEL_NAME = "The Claw Bay";
|
|
43
47
|
const TRAE_PATCH_MARKER = "theclawbay-trae-patch";
|
|
44
48
|
const TRAE_BUNDLE_BACKUP_SUFFIX = ".theclawbay-managed-backup";
|
|
@@ -179,6 +183,18 @@ async function writeJsonFile(filePath, value, mode) {
|
|
|
179
183
|
if (mode !== undefined)
|
|
180
184
|
await promises_1.default.chmod(filePath, mode);
|
|
181
185
|
}
|
|
186
|
+
function isManagedOpenCodeModel(value) {
|
|
187
|
+
if (typeof value !== "string")
|
|
188
|
+
return false;
|
|
189
|
+
if (!value.startsWith(`${OPENAI_PROVIDER_ID}/`))
|
|
190
|
+
return false;
|
|
191
|
+
return SUPPORTED_MODEL_IDS.has(value.slice(`${OPENAI_PROVIDER_ID}/`.length));
|
|
192
|
+
}
|
|
193
|
+
function isManagedOpenCodeProvider(value) {
|
|
194
|
+
const provider = objectRecordOr(value, {});
|
|
195
|
+
const options = objectRecordOr(provider.options, {});
|
|
196
|
+
return isTheClawBayOpenAiCompatibleBaseUrl(options.baseURL);
|
|
197
|
+
}
|
|
182
198
|
function roamingAppDataDir() {
|
|
183
199
|
if (process.env.APPDATA?.trim())
|
|
184
200
|
return process.env.APPDATA;
|
|
@@ -433,11 +449,11 @@ async function cleanupOpenClawConfig() {
|
|
|
433
449
|
await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
434
450
|
return true;
|
|
435
451
|
}
|
|
436
|
-
async function
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
452
|
+
async function cleanupOpenCodeFamilyConfig(params) {
|
|
453
|
+
const existingRaw = await readFileIfExists(params.configPath);
|
|
454
|
+
if (existingRaw === null || !existingRaw.trim()) {
|
|
455
|
+
return removeFileIfExists(params.restoreStatePath);
|
|
456
|
+
}
|
|
441
457
|
let doc;
|
|
442
458
|
try {
|
|
443
459
|
doc = objectRecordOr(JSON.parse(existingRaw), {});
|
|
@@ -445,56 +461,76 @@ async function cleanupOpenCodeConfig() {
|
|
|
445
461
|
catch {
|
|
446
462
|
return false;
|
|
447
463
|
}
|
|
464
|
+
const snapshotRaw = await readFileIfExists(params.restoreStatePath);
|
|
448
465
|
let changed = false;
|
|
449
466
|
const providerRoot = objectRecordOr(doc.provider, {});
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
delete providerRoot[id];
|
|
453
|
-
changed = true;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
doc.provider = providerRoot;
|
|
457
|
-
const model = doc.model;
|
|
458
|
-
if (typeof model === "string" && model.startsWith(`${DEFAULT_PROVIDER_ID}/`)) {
|
|
459
|
-
delete doc.model;
|
|
467
|
+
if (DEFAULT_PROVIDER_ID in providerRoot) {
|
|
468
|
+
delete providerRoot[DEFAULT_PROVIDER_ID];
|
|
460
469
|
changed = true;
|
|
461
470
|
}
|
|
462
|
-
if (
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
471
|
+
if (snapshotRaw?.trim()) {
|
|
472
|
+
try {
|
|
473
|
+
const snapshot = JSON.parse(snapshotRaw);
|
|
474
|
+
if (snapshot.openAiProvider) {
|
|
475
|
+
providerRoot[OPENAI_PROVIDER_ID] = snapshot.openAiProvider;
|
|
476
|
+
}
|
|
477
|
+
else if (OPENAI_PROVIDER_ID in providerRoot) {
|
|
478
|
+
delete providerRoot[OPENAI_PROVIDER_ID];
|
|
479
|
+
}
|
|
480
|
+
doc.provider = providerRoot;
|
|
481
|
+
if (snapshot.model === null) {
|
|
482
|
+
if (isManagedOpenCodeModel(doc.model) || typeof doc.model === "string" && doc.model.startsWith(`${DEFAULT_PROVIDER_ID}/`)) {
|
|
483
|
+
delete doc.model;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
doc.model = snapshot.model;
|
|
488
|
+
}
|
|
489
|
+
if (snapshot.schema === null) {
|
|
490
|
+
if (typeof doc.$schema === "string")
|
|
491
|
+
delete doc.$schema;
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
doc.$schema = snapshot.schema;
|
|
495
|
+
}
|
|
484
496
|
changed = true;
|
|
497
|
+
await promises_1.default.writeFile(params.configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
498
|
+
await removeFileIfExists(params.restoreStatePath);
|
|
499
|
+
return changed;
|
|
500
|
+
}
|
|
501
|
+
catch {
|
|
502
|
+
// Fall through to best-effort cleanup below.
|
|
485
503
|
}
|
|
486
504
|
}
|
|
487
505
|
doc.provider = providerRoot;
|
|
488
506
|
const model = doc.model;
|
|
489
|
-
if (typeof model === "string" && model.startsWith(`${DEFAULT_PROVIDER_ID}/`)) {
|
|
507
|
+
if (typeof model === "string" && (model.startsWith(`${DEFAULT_PROVIDER_ID}/`) || isManagedOpenCodeModel(model))) {
|
|
490
508
|
delete doc.model;
|
|
491
509
|
changed = true;
|
|
492
510
|
}
|
|
511
|
+
if (isManagedOpenCodeProvider(providerRoot[OPENAI_PROVIDER_ID])) {
|
|
512
|
+
delete providerRoot[OPENAI_PROVIDER_ID];
|
|
513
|
+
doc.provider = providerRoot;
|
|
514
|
+
changed = true;
|
|
515
|
+
}
|
|
493
516
|
if (!changed)
|
|
494
517
|
return false;
|
|
495
|
-
await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
518
|
+
await promises_1.default.writeFile(params.configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
519
|
+
await removeFileIfExists(params.restoreStatePath);
|
|
496
520
|
return true;
|
|
497
521
|
}
|
|
522
|
+
async function cleanupOpenCodeConfig() {
|
|
523
|
+
return cleanupOpenCodeFamilyConfig({
|
|
524
|
+
configPath: node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "opencode.json"),
|
|
525
|
+
restoreStatePath: OPENCODE_RESTORE_STATE_PATH,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
async function cleanupKiloConfig() {
|
|
529
|
+
return cleanupOpenCodeFamilyConfig({
|
|
530
|
+
configPath: node_path_1.default.join(node_os_1.default.homedir(), ".config", "kilo", "opencode.json"),
|
|
531
|
+
restoreStatePath: KILO_RESTORE_STATE_PATH,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
498
534
|
async function cleanupTraeBundle() {
|
|
499
535
|
const bundlePath = traeBundlePath();
|
|
500
536
|
if (!bundlePath)
|
|
@@ -678,10 +714,10 @@ class LogoutCommand extends base_command_1.BaseCommand {
|
|
|
678
714
|
this.log("- Codex login state: no cleanup needed.");
|
|
679
715
|
}
|
|
680
716
|
if (modelCacheCleanup.action === "removed") {
|
|
681
|
-
this.log("- Codex model cache: removed the Claw Bay GPT-5.4 seed.");
|
|
717
|
+
this.log("- Codex model cache: removed the Claw Bay GPT-5.4 / GPT-5.4 Mini seed.");
|
|
682
718
|
}
|
|
683
719
|
else if (modelCacheCleanup.action === "preserved") {
|
|
684
|
-
this.log("- Codex model cache: preserved an existing GPT-5.4 entry.");
|
|
720
|
+
this.log("- Codex model cache: preserved an existing GPT-5.4 / GPT-5.4 Mini entry.");
|
|
685
721
|
}
|
|
686
722
|
else if (modelCacheCleanup.warning) {
|
|
687
723
|
this.log(`- Codex model cache: ${modelCacheCleanup.warning}`);
|
package/dist/commands/setup.js
CHANGED
|
@@ -42,6 +42,8 @@ const CLINE_GLOBAL_STATE_PATH = node_path_1.default.join(node_os_1.default.homed
|
|
|
42
42
|
const CLINE_SECRETS_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".cline", "data", "secrets.json");
|
|
43
43
|
const AIDER_CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".aider.conf.yml");
|
|
44
44
|
const CLINE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "cline.restore.json");
|
|
45
|
+
const OPENCODE_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "opencode.restore.json");
|
|
46
|
+
const KILO_RESTORE_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "kilo.restore.json");
|
|
45
47
|
const ROO_SETTINGS_STATE_PATH = node_path_1.default.join(paths_1.theclawbayStateDir, "roo-settings.restore.json");
|
|
46
48
|
const ROO_IMPORT_FILE = node_path_1.default.join(paths_1.theclawbayConfigDir, "roo-code-settings.json");
|
|
47
49
|
const MIGRATION_STATE_FILE = node_path_1.default.join(paths_1.codexDir, "theclawbay.migration.json");
|
|
@@ -56,6 +58,9 @@ const HISTORY_PROVIDER_NEUTRALIZE_SOURCES = new Set(["openai", "theclawbay-wan",
|
|
|
56
58
|
const HISTORY_PROVIDER_DB_MIGRATE_SOURCES = ["openai", "theclawbay-wan"];
|
|
57
59
|
const SETUP_CLIENT_IDS = ["codex", "continue", "cline", "openclaw", "opencode", "kilo", "roo", "trae", "aider"];
|
|
58
60
|
const THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
|
|
61
|
+
const OPENAI_PROVIDER_ID = "openai";
|
|
62
|
+
const OPENCODE_CONFIG_SCHEMA_URL = "https://opencode.ai/config.json";
|
|
63
|
+
const KILO_CONFIG_SCHEMA_URL = "https://kilo.ai/config.json";
|
|
59
64
|
const CONTINUE_MODEL_NAME = "The Claw Bay";
|
|
60
65
|
const ROO_PROFILE_NAME = "The Claw Bay";
|
|
61
66
|
const ROO_PROFILE_ID = "theclawbay-openai-compatible";
|
|
@@ -460,25 +465,51 @@ async function promptForSetupClients(clients) {
|
|
|
460
465
|
const wasRaw = stdin.isRaw;
|
|
461
466
|
const applyIndex = options.length;
|
|
462
467
|
const hyperlinksEnabled = supportsTerminalHyperlinks();
|
|
468
|
+
const ansi = {
|
|
469
|
+
reset: "\x1b[0m",
|
|
470
|
+
bold: "\x1b[1m",
|
|
471
|
+
dim: "\x1b[2m",
|
|
472
|
+
inverse: "\x1b[7m",
|
|
473
|
+
red: "\x1b[31m",
|
|
474
|
+
green: "\x1b[32m",
|
|
475
|
+
yellow: "\x1b[33m",
|
|
476
|
+
gray: "\x1b[90m",
|
|
477
|
+
};
|
|
478
|
+
const paint = (text, ...codes) => `${codes.join("")}${text}${ansi.reset}`;
|
|
463
479
|
const clearScreen = () => {
|
|
464
480
|
stdout.write("\x1b[2J\x1b[H");
|
|
465
481
|
};
|
|
466
482
|
const selectedCount = () => options.filter((option) => option.checked).length;
|
|
467
483
|
const render = () => {
|
|
468
484
|
clearScreen();
|
|
469
|
-
stdout.write("Choose local clients to configure\n
|
|
470
|
-
stdout.write(`Use ↑/↓ to move, Enter to toggle the highlighted integration, or press 1-${options.length} to toggle directly
|
|
471
|
-
stdout.write("Each tool name links to its official site when your terminal supports it
|
|
472
|
-
stdout.write("Move to Apply setup and press Enter when you're ready
|
|
485
|
+
stdout.write(`${paint("Choose local clients to configure", ansi.bold)}\n`);
|
|
486
|
+
stdout.write(`${paint(`Use ↑/↓ to move, Enter to toggle the highlighted integration, or press 1-${options.length} to toggle directly.`, ansi.dim, ansi.gray)}\n`);
|
|
487
|
+
stdout.write(`${paint("Each tool name links to its official site when your terminal supports it.", ansi.dim, ansi.gray)}\n`);
|
|
488
|
+
stdout.write(`${paint("Move to Apply setup and press Enter when you're ready.", ansi.dim, ansi.gray)}\n\n`);
|
|
473
489
|
for (const [index, option] of options.entries()) {
|
|
474
|
-
const pointer = index === cursor ? ">" : " ";
|
|
475
|
-
const mark = option.detected
|
|
476
|
-
|
|
477
|
-
|
|
490
|
+
const pointer = index === cursor ? paint(">", ansi.bold) : " ";
|
|
491
|
+
const mark = option.detected
|
|
492
|
+
? option.checked
|
|
493
|
+
? paint("[x]", ansi.green, ansi.bold)
|
|
494
|
+
: paint("[ ]", ansi.gray)
|
|
495
|
+
: paint("[-]", ansi.red, ansi.bold);
|
|
496
|
+
const badge = option.detected
|
|
497
|
+
? option.recommended
|
|
498
|
+
? paint("recommended", ansi.green)
|
|
499
|
+
: paint("optional", ansi.yellow)
|
|
500
|
+
: paint("not detected", ansi.red);
|
|
501
|
+
const label = option.detected
|
|
502
|
+
? formatSetupClientLabel(option, hyperlinksEnabled)
|
|
503
|
+
: paint(formatSetupClientLabel(option, hyperlinksEnabled), ansi.dim, ansi.gray);
|
|
504
|
+
stdout.write(`${pointer} ${index + 1}. ${mark} ${label} ${badge}\n`);
|
|
478
505
|
}
|
|
479
506
|
const applyPointer = cursor === applyIndex ? ">" : " ";
|
|
480
|
-
|
|
481
|
-
|
|
507
|
+
const applyLabel = `Apply setup (${selectedCount()} selected)`;
|
|
508
|
+
const applyStyled = cursor === applyIndex
|
|
509
|
+
? paint(` ${applyLabel} `, ansi.inverse, ansi.bold, ansi.green)
|
|
510
|
+
: paint(applyLabel, ansi.bold, ansi.green);
|
|
511
|
+
stdout.write(`\n${applyPointer} ${applyStyled}\n`);
|
|
512
|
+
stdout.write(`\n${paint(hint, ansi.dim, ansi.gray)}\n`);
|
|
482
513
|
};
|
|
483
514
|
const finish = () => {
|
|
484
515
|
if (settled)
|
|
@@ -992,7 +1023,7 @@ async function patchOpenClawConfigFile(params) {
|
|
|
992
1023
|
baseUrl: `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy/v1`,
|
|
993
1024
|
apiKey: params.apiKey,
|
|
994
1025
|
api: "openai-responses",
|
|
995
|
-
models: params.models,
|
|
1026
|
+
models: buildOpenClawModels(params.models),
|
|
996
1027
|
};
|
|
997
1028
|
const configPath = node_path_1.default.join(node_os_1.default.homedir(), ".openclaw", "openclaw.json");
|
|
998
1029
|
await promises_1.default.mkdir(node_path_1.default.dirname(configPath), { recursive: true });
|
|
@@ -1024,6 +1055,9 @@ async function patchOpenClawConfigFile(params) {
|
|
|
1024
1055
|
const defaultsRoot = objectRecordOr(agentsRoot.defaults, {});
|
|
1025
1056
|
const modelRoot = objectRecordOr(defaultsRoot.model, {});
|
|
1026
1057
|
modelRoot.primary = resolveOpenClawPrimaryModel(params);
|
|
1058
|
+
if (typeof defaultsRoot.thinkingDefault !== "string") {
|
|
1059
|
+
defaultsRoot.thinkingDefault = "medium";
|
|
1060
|
+
}
|
|
1027
1061
|
defaultsRoot.model = modelRoot;
|
|
1028
1062
|
agentsRoot.defaults = defaultsRoot;
|
|
1029
1063
|
doc.agents = agentsRoot;
|
|
@@ -1040,21 +1074,57 @@ function objectRecordOr(value, fallback) {
|
|
|
1040
1074
|
}
|
|
1041
1075
|
return fallback;
|
|
1042
1076
|
}
|
|
1043
|
-
function
|
|
1077
|
+
function modelContextLimit(modelId) {
|
|
1078
|
+
if (modelId === "gpt-5.4")
|
|
1079
|
+
return 1050000;
|
|
1080
|
+
return 272000;
|
|
1081
|
+
}
|
|
1082
|
+
function modelOutputLimit(modelId) {
|
|
1083
|
+
if (modelId === "gpt-5.4")
|
|
1084
|
+
return 128000;
|
|
1085
|
+
return 65536;
|
|
1086
|
+
}
|
|
1087
|
+
function buildOpenCodeModelConfig(model) {
|
|
1088
|
+
return {
|
|
1089
|
+
name: model.name || model.id,
|
|
1090
|
+
reasoning: true,
|
|
1091
|
+
tool_call: true,
|
|
1092
|
+
attachment: true,
|
|
1093
|
+
modalities: {
|
|
1094
|
+
input: ["text", "image"],
|
|
1095
|
+
output: ["text"],
|
|
1096
|
+
},
|
|
1097
|
+
limit: {
|
|
1098
|
+
context: modelContextLimit(model.id),
|
|
1099
|
+
output: modelOutputLimit(model.id),
|
|
1100
|
+
},
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function buildOpenCodeModelsObject(models) {
|
|
1044
1104
|
const result = {};
|
|
1045
1105
|
for (const model of models) {
|
|
1046
1106
|
if (!model.id)
|
|
1047
1107
|
continue;
|
|
1048
|
-
result[model.id] =
|
|
1108
|
+
result[model.id] = buildOpenCodeModelConfig(model);
|
|
1049
1109
|
}
|
|
1050
1110
|
return result;
|
|
1051
1111
|
}
|
|
1052
|
-
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1112
|
+
function isManagedOpenCodeProvider(provider) {
|
|
1113
|
+
const options = objectRecordOr(provider.options, {});
|
|
1114
|
+
return isTheClawBayOpenAiCompatibleBaseUrl(options.baseURL);
|
|
1115
|
+
}
|
|
1116
|
+
function isManagedOpenCodeModel(value, supportedModelIds) {
|
|
1117
|
+
if (typeof value !== "string")
|
|
1118
|
+
return false;
|
|
1119
|
+
if (!value.startsWith(`${OPENAI_PROVIDER_ID}/`))
|
|
1120
|
+
return false;
|
|
1121
|
+
return supportedModelIds.has(value.slice(`${OPENAI_PROVIDER_ID}/`.length));
|
|
1122
|
+
}
|
|
1123
|
+
async function writeOpenCodeFamilyConfig(params) {
|
|
1124
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(params.configPath), { recursive: true });
|
|
1055
1125
|
let existingRaw = "";
|
|
1056
1126
|
try {
|
|
1057
|
-
existingRaw = await promises_1.default.readFile(configPath, "utf8");
|
|
1127
|
+
existingRaw = await promises_1.default.readFile(params.configPath, "utf8");
|
|
1058
1128
|
}
|
|
1059
1129
|
catch (error) {
|
|
1060
1130
|
const err = error;
|
|
@@ -1067,60 +1137,81 @@ async function writeOpenCodeConfig(params) {
|
|
|
1067
1137
|
doc = objectRecordOr(JSON.parse(existingRaw), {});
|
|
1068
1138
|
}
|
|
1069
1139
|
catch {
|
|
1070
|
-
throw new Error(`invalid JSON in
|
|
1140
|
+
throw new Error(`invalid JSON in config: ${params.configPath}`);
|
|
1071
1141
|
}
|
|
1072
1142
|
}
|
|
1073
1143
|
const providerRoot = objectRecordOr(doc.provider, {});
|
|
1074
|
-
providerRoot[
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1144
|
+
const openAiProvider = objectRecordOr(providerRoot[OPENAI_PROVIDER_ID], {});
|
|
1145
|
+
const supportedModelIds = new Set(params.models.map((entry) => entry.id).filter(Boolean));
|
|
1146
|
+
const alreadyManaged = isManagedOpenCodeProvider(openAiProvider) && isManagedOpenCodeModel(doc.model, supportedModelIds);
|
|
1147
|
+
if (!alreadyManaged) {
|
|
1148
|
+
const snapshot = {
|
|
1149
|
+
openAiProvider: Object.keys(openAiProvider).length > 0 ? openAiProvider : null,
|
|
1150
|
+
model: typeof doc.model === "string" && !doc.model.startsWith(`${DEFAULT_PROVIDER_ID}/`)
|
|
1151
|
+
? doc.model
|
|
1152
|
+
: null,
|
|
1153
|
+
schema: typeof doc.$schema === "string" ? doc.$schema : null,
|
|
1154
|
+
};
|
|
1155
|
+
await writeJsonObjectFile(params.restoreStatePath, snapshot, 0o600);
|
|
1156
|
+
}
|
|
1157
|
+
const managedOpenAiProvider = objectRecordOr(providerRoot[OPENAI_PROVIDER_ID], {});
|
|
1158
|
+
const optionsRoot = objectRecordOr(managedOpenAiProvider.options, {});
|
|
1159
|
+
optionsRoot.baseURL = `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy/v1`;
|
|
1160
|
+
optionsRoot.apiKey = params.apiKey;
|
|
1161
|
+
managedOpenAiProvider.options = optionsRoot;
|
|
1162
|
+
managedOpenAiProvider.models = buildOpenCodeModelsObject(params.models);
|
|
1163
|
+
managedOpenAiProvider.whitelist = params.models.map((entry) => entry.id).filter(Boolean);
|
|
1164
|
+
if ("blacklist" in managedOpenAiProvider) {
|
|
1165
|
+
delete managedOpenAiProvider.blacklist;
|
|
1166
|
+
}
|
|
1167
|
+
providerRoot[OPENAI_PROVIDER_ID] = managedOpenAiProvider;
|
|
1168
|
+
if (DEFAULT_PROVIDER_ID in providerRoot) {
|
|
1169
|
+
delete providerRoot[DEFAULT_PROVIDER_ID];
|
|
1170
|
+
}
|
|
1171
|
+
doc.$schema = params.schemaUrl;
|
|
1083
1172
|
doc.provider = providerRoot;
|
|
1084
|
-
doc.model = `${
|
|
1085
|
-
await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
1086
|
-
return configPath;
|
|
1173
|
+
doc.model = `${OPENAI_PROVIDER_ID}/${params.model}`;
|
|
1174
|
+
await promises_1.default.writeFile(params.configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
1175
|
+
return params.configPath;
|
|
1176
|
+
}
|
|
1177
|
+
function buildOpenClawModels(models) {
|
|
1178
|
+
const supportedModelMap = new Map((0, supported_models_1.getSupportedModels)().map((model) => [model.id, model]));
|
|
1179
|
+
return models
|
|
1180
|
+
.filter((model) => Boolean(model.id))
|
|
1181
|
+
.map((model) => {
|
|
1182
|
+
const pricing = supportedModelMap.get(model.id);
|
|
1183
|
+
return {
|
|
1184
|
+
id: model.id,
|
|
1185
|
+
name: model.name || model.id,
|
|
1186
|
+
api: "openai-responses",
|
|
1187
|
+
reasoning: true,
|
|
1188
|
+
input: ["text", "image"],
|
|
1189
|
+
cost: {
|
|
1190
|
+
input: pricing?.inputPer1M ?? 0,
|
|
1191
|
+
output: pricing?.outputPer1M ?? 0,
|
|
1192
|
+
cacheRead: pricing?.cachedInputPer1M ?? 0,
|
|
1193
|
+
cacheWrite: pricing?.inputPer1M ?? 0,
|
|
1194
|
+
},
|
|
1195
|
+
contextWindow: modelContextLimit(model.id),
|
|
1196
|
+
maxTokens: modelOutputLimit(model.id),
|
|
1197
|
+
};
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
async function writeOpenCodeConfig(params) {
|
|
1201
|
+
return writeOpenCodeFamilyConfig({
|
|
1202
|
+
configPath: node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "opencode.json"),
|
|
1203
|
+
restoreStatePath: OPENCODE_RESTORE_STATE_PATH,
|
|
1204
|
+
schemaUrl: OPENCODE_CONFIG_SCHEMA_URL,
|
|
1205
|
+
...params,
|
|
1206
|
+
});
|
|
1087
1207
|
}
|
|
1088
1208
|
async function writeKiloConfig(params) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
}
|
|
1095
|
-
catch (error) {
|
|
1096
|
-
const err = error;
|
|
1097
|
-
if (err.code !== "ENOENT")
|
|
1098
|
-
throw error;
|
|
1099
|
-
}
|
|
1100
|
-
let doc = {};
|
|
1101
|
-
if (existingRaw.trim()) {
|
|
1102
|
-
try {
|
|
1103
|
-
doc = objectRecordOr(JSON.parse(existingRaw), {});
|
|
1104
|
-
}
|
|
1105
|
-
catch {
|
|
1106
|
-
throw new Error(`invalid JSON in Kilo config: ${configPath}`);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
const providerRoot = objectRecordOr(doc.provider, {});
|
|
1110
|
-
providerRoot[DEFAULT_PROVIDER_ID] = {
|
|
1111
|
-
npm: "@ai-sdk/openai-compatible",
|
|
1112
|
-
name: DEFAULT_PROVIDER_ID,
|
|
1113
|
-
options: {
|
|
1114
|
-
baseURL: `${trimTrailingSlash(params.backendUrl)}/api/codex-auth/v1/proxy/v1`,
|
|
1115
|
-
apiKey: params.apiKey,
|
|
1116
|
-
},
|
|
1117
|
-
models: openCodeModelsObject(params.models),
|
|
1118
|
-
};
|
|
1119
|
-
doc.$schema = "https://kilo.ai/config.json";
|
|
1120
|
-
doc.provider = providerRoot;
|
|
1121
|
-
doc.model = `${DEFAULT_PROVIDER_ID}/${params.model || DEFAULT_KILO_MODEL}`;
|
|
1122
|
-
await promises_1.default.writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
1123
|
-
return configPath;
|
|
1209
|
+
return writeOpenCodeFamilyConfig({
|
|
1210
|
+
configPath: node_path_1.default.join(node_os_1.default.homedir(), ".config", "kilo", "opencode.json"),
|
|
1211
|
+
restoreStatePath: KILO_RESTORE_STATE_PATH,
|
|
1212
|
+
schemaUrl: KILO_CONFIG_SCHEMA_URL,
|
|
1213
|
+
...params,
|
|
1214
|
+
});
|
|
1124
1215
|
}
|
|
1125
1216
|
function traePatchSnippet(params) {
|
|
1126
1217
|
const patchedModels = params.models.length
|
|
@@ -1467,19 +1558,19 @@ class SetupCommand extends base_command_1.BaseCommand {
|
|
|
1467
1558
|
this.log(`- Conversation cache: could not update local Codex history DB.${detail}`);
|
|
1468
1559
|
}
|
|
1469
1560
|
if (modelCacheMigration?.action === "seeded") {
|
|
1470
|
-
this.log("- Codex model cache: added GPT-5.4 to the local picker cache.");
|
|
1561
|
+
this.log("- Codex model cache: added GPT-5.4 / GPT-5.4 Mini to the local picker cache.");
|
|
1471
1562
|
}
|
|
1472
1563
|
else if (modelCacheMigration?.action === "created") {
|
|
1473
|
-
this.log("- Codex model cache: created a local picker cache with GPT-5.4.");
|
|
1564
|
+
this.log("- Codex model cache: created a local picker cache with GPT-5.4 / GPT-5.4 Mini.");
|
|
1474
1565
|
}
|
|
1475
1566
|
else if (modelCacheMigration?.action === "refreshed") {
|
|
1476
|
-
this.log("- Codex model cache: refreshed the local picker cache for GPT-5.4.");
|
|
1567
|
+
this.log("- Codex model cache: refreshed the local picker cache for GPT-5.4 / GPT-5.4 Mini.");
|
|
1477
1568
|
}
|
|
1478
1569
|
else if (modelCacheMigration?.action === "already_seeded") {
|
|
1479
|
-
this.log("- Codex model cache: GPT-5.4
|
|
1570
|
+
this.log("- Codex model cache: GPT-5.4 / GPT-5.4 Mini were already seeded locally.");
|
|
1480
1571
|
}
|
|
1481
1572
|
else if (modelCacheMigration?.action === "already_present") {
|
|
1482
|
-
this.log("- Codex model cache: GPT-5.4 already existed locally.");
|
|
1573
|
+
this.log("- Codex model cache: GPT-5.4 / GPT-5.4 Mini already existed locally.");
|
|
1483
1574
|
}
|
|
1484
1575
|
else if (modelCacheMigration?.warning) {
|
|
1485
1576
|
this.log(`- Codex model cache: ${modelCacheMigration.warning}`);
|
|
@@ -14,10 +14,13 @@ const supported_models_1 = require("./supported-models");
|
|
|
14
14
|
const MODELS_CACHE_FILE = "models_cache.json";
|
|
15
15
|
const MODELS_CACHE_STATE_FILE = "theclawbay.models-cache.json";
|
|
16
16
|
const STATE_DB_FILE_PATTERN = /^state_\d+\.sqlite$/;
|
|
17
|
-
const
|
|
17
|
+
const TARGET_MODEL_IDS = ["gpt-5.4", "gpt-5.4-mini"];
|
|
18
|
+
const TARGET_MODEL_ID_SET = new Set(TARGET_MODEL_IDS);
|
|
18
19
|
const SEED_MARKER_KEY = "_theclawbay_seeded";
|
|
19
|
-
|
|
20
|
-
const
|
|
20
|
+
// Older releases stored the model id as the marker value; accept those so cleanup still works.
|
|
21
|
+
const SEED_MARKER_VALUE = "theclawbay";
|
|
22
|
+
const LEGACY_SEED_MARKER_VALUES = new Set(["gpt-5.4", SEED_MARKER_VALUE, true]);
|
|
23
|
+
const TEMPLATE_MODEL_IDS = (0, supported_models_1.getSupportedModelIds)().filter((id) => !TARGET_MODEL_ID_SET.has(id));
|
|
21
24
|
const DEFAULT_REASONING_LEVELS = [
|
|
22
25
|
{ effort: "low", description: "Fast responses with lighter reasoning" },
|
|
23
26
|
{ effort: "medium", description: "Balances speed and reasoning depth for everyday tasks" },
|
|
@@ -88,29 +91,66 @@ async function removeFileIfExists(filePath) {
|
|
|
88
91
|
throw error;
|
|
89
92
|
}
|
|
90
93
|
}
|
|
94
|
+
function normalizePatchFingerprintMap(state) {
|
|
95
|
+
if (!state)
|
|
96
|
+
return {};
|
|
97
|
+
if (state.version === 1) {
|
|
98
|
+
if (!TARGET_MODEL_ID_SET.has(state.modelId))
|
|
99
|
+
return {};
|
|
100
|
+
return state.fingerprint ? { [state.modelId]: state.fingerprint } : {};
|
|
101
|
+
}
|
|
102
|
+
const output = {};
|
|
103
|
+
for (const entry of state.models) {
|
|
104
|
+
if (!TARGET_MODEL_ID_SET.has(entry.modelId))
|
|
105
|
+
continue;
|
|
106
|
+
if (entry.fingerprint)
|
|
107
|
+
output[entry.modelId] = entry.fingerprint;
|
|
108
|
+
}
|
|
109
|
+
return output;
|
|
110
|
+
}
|
|
91
111
|
async function readPatchState(statePath) {
|
|
92
112
|
const parsed = await readJsonIfExists(statePath);
|
|
93
113
|
const obj = objectRecordOr(parsed);
|
|
94
114
|
if (!obj)
|
|
95
|
-
return
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
115
|
+
return {};
|
|
116
|
+
const version = obj.version;
|
|
117
|
+
if (version === 1) {
|
|
118
|
+
const modelId = typeof obj.modelId === "string" ? obj.modelId : "";
|
|
119
|
+
const fingerprint = typeof obj.fingerprint === "string" ? obj.fingerprint : "";
|
|
120
|
+
if (!modelId || !fingerprint)
|
|
121
|
+
return {};
|
|
122
|
+
return normalizePatchFingerprintMap({ version: 1, modelId, fingerprint });
|
|
123
|
+
}
|
|
124
|
+
if (version === 2) {
|
|
125
|
+
const modelsRaw = obj.models;
|
|
126
|
+
if (!Array.isArray(modelsRaw))
|
|
127
|
+
return {};
|
|
128
|
+
const models = [];
|
|
129
|
+
for (const entry of modelsRaw) {
|
|
130
|
+
const record = objectRecordOr(entry);
|
|
131
|
+
if (!record)
|
|
132
|
+
continue;
|
|
133
|
+
const modelId = typeof record.modelId === "string" ? record.modelId : "";
|
|
134
|
+
const fingerprint = typeof record.fingerprint === "string" ? record.fingerprint : "";
|
|
135
|
+
if (!modelId || !fingerprint)
|
|
136
|
+
continue;
|
|
137
|
+
models.push({ modelId, fingerprint });
|
|
138
|
+
}
|
|
139
|
+
return normalizePatchFingerprintMap({ version: 2, models });
|
|
140
|
+
}
|
|
141
|
+
return {};
|
|
107
142
|
}
|
|
108
|
-
async function writePatchState(statePath,
|
|
143
|
+
async function writePatchState(statePath, fingerprints) {
|
|
109
144
|
await promises_1.default.mkdir(node_path_1.default.dirname(statePath), { recursive: true });
|
|
145
|
+
const models = TARGET_MODEL_IDS.flatMap((modelId) => {
|
|
146
|
+
const fingerprint = fingerprints[modelId];
|
|
147
|
+
if (!fingerprint)
|
|
148
|
+
return [];
|
|
149
|
+
return [{ modelId, fingerprint }];
|
|
150
|
+
});
|
|
110
151
|
const contents = JSON.stringify({
|
|
111
|
-
version:
|
|
112
|
-
|
|
113
|
-
fingerprint,
|
|
152
|
+
version: 2,
|
|
153
|
+
models,
|
|
114
154
|
}, null, 2);
|
|
115
155
|
await promises_1.default.writeFile(statePath, `${contents}\n`, "utf8");
|
|
116
156
|
}
|
|
@@ -118,18 +158,23 @@ function findModel(models, slug) {
|
|
|
118
158
|
return models.find((entry) => entry.slug === slug) ?? null;
|
|
119
159
|
}
|
|
120
160
|
function hasSeedMarker(model) {
|
|
121
|
-
return model[SEED_MARKER_KEY]
|
|
161
|
+
return LEGACY_SEED_MARKER_VALUES.has(model[SEED_MARKER_KEY]);
|
|
122
162
|
}
|
|
123
163
|
function applySeedMarker(model) {
|
|
124
164
|
const next = cloneJson(model);
|
|
125
165
|
next[SEED_MARKER_KEY] = SEED_MARKER_VALUE;
|
|
126
166
|
return next;
|
|
127
167
|
}
|
|
128
|
-
function
|
|
168
|
+
function seedDescription(modelId) {
|
|
169
|
+
if (modelId === "gpt-5.4-mini")
|
|
170
|
+
return "Smaller GPT-5.4 variant for quick iterations.";
|
|
171
|
+
return "Latest frontier agentic coding model.";
|
|
172
|
+
}
|
|
173
|
+
function normalizeSeedModel(template, modelId) {
|
|
129
174
|
const seed = applySeedMarker(template ?? {});
|
|
130
|
-
seed.slug =
|
|
131
|
-
seed.display_name =
|
|
132
|
-
seed.description =
|
|
175
|
+
seed.slug = modelId;
|
|
176
|
+
seed.display_name = modelId;
|
|
177
|
+
seed.description = seedDescription(modelId);
|
|
133
178
|
if (typeof seed.default_reasoning_level !== "string") {
|
|
134
179
|
seed.default_reasoning_level = "medium";
|
|
135
180
|
}
|
|
@@ -182,13 +227,13 @@ function normalizeSeedModel(template) {
|
|
|
182
227
|
}
|
|
183
228
|
return seed;
|
|
184
229
|
}
|
|
185
|
-
function buildSeedModel(models) {
|
|
230
|
+
function buildSeedModel(models, modelId) {
|
|
186
231
|
for (const candidate of TEMPLATE_MODEL_IDS) {
|
|
187
232
|
const template = findModel(models, candidate);
|
|
188
233
|
if (template)
|
|
189
|
-
return normalizeSeedModel(template);
|
|
234
|
+
return normalizeSeedModel(template, modelId);
|
|
190
235
|
}
|
|
191
|
-
return normalizeSeedModel(null);
|
|
236
|
+
return normalizeSeedModel(null, modelId);
|
|
192
237
|
}
|
|
193
238
|
function setCacheFreshnessMetadata(doc, clientVersion) {
|
|
194
239
|
let changed = false;
|
|
@@ -342,70 +387,115 @@ async function ensureCodexModelCacheHasGpt54(params) {
|
|
|
342
387
|
await removeFileIfExists(statePath);
|
|
343
388
|
return {
|
|
344
389
|
action: "skipped",
|
|
345
|
-
warning: "Codex models cache was not found, and Codex version could not be inferred for a safe GPT-5.4 seed.",
|
|
390
|
+
warning: "Codex models cache was not found, and Codex version could not be inferred for a safe GPT-5.4 / GPT-5.4 Mini seed.",
|
|
346
391
|
};
|
|
347
392
|
}
|
|
348
|
-
const
|
|
393
|
+
const docModels = [];
|
|
394
|
+
const nextState = {};
|
|
395
|
+
for (const modelId of TARGET_MODEL_IDS) {
|
|
396
|
+
const seed = buildSeedModel(docModels, modelId);
|
|
397
|
+
docModels.push(seed);
|
|
398
|
+
nextState[modelId] = fingerprintModel(seed);
|
|
399
|
+
}
|
|
349
400
|
const doc = {
|
|
350
401
|
fetched_at: new Date().toISOString(),
|
|
351
402
|
client_version: clientVersion,
|
|
352
|
-
models:
|
|
403
|
+
models: docModels,
|
|
353
404
|
};
|
|
354
405
|
await promises_1.default.mkdir(params.codexHome, { recursive: true });
|
|
355
406
|
await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
356
|
-
await writePatchState(statePath,
|
|
407
|
+
await writePatchState(statePath, nextState);
|
|
357
408
|
return { action: "created" };
|
|
358
409
|
}
|
|
359
410
|
const doc = normalizeCacheDocument(parsed);
|
|
360
411
|
if (!doc) {
|
|
361
412
|
return {
|
|
362
413
|
action: "skipped",
|
|
363
|
-
warning: "Codex models cache exists but is not valid JSON in the expected format; skipped GPT-5.4 seed.",
|
|
414
|
+
warning: "Codex models cache exists but is not valid JSON in the expected format; skipped GPT-5.4 / GPT-5.4 Mini seed.",
|
|
364
415
|
};
|
|
365
416
|
}
|
|
366
417
|
const clientVersion = (await inferCodexClientVersion(params.codexHome)) ??
|
|
367
418
|
(typeof doc.client_version === "string" && doc.client_version ? doc.client_version : null);
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
384
|
-
return { action: "refreshed" };
|
|
419
|
+
let changed = false;
|
|
420
|
+
let seeded = false;
|
|
421
|
+
let stateChanged = false;
|
|
422
|
+
const nextState = { ...existingState };
|
|
423
|
+
for (const modelId of TARGET_MODEL_IDS) {
|
|
424
|
+
const existingModel = findModel(doc.models, modelId);
|
|
425
|
+
const trackedFingerprint = existingState[modelId];
|
|
426
|
+
if (existingModel) {
|
|
427
|
+
const currentFingerprint = fingerprintModel(existingModel);
|
|
428
|
+
if (hasSeedMarker(existingModel)) {
|
|
429
|
+
if (!trackedFingerprint || trackedFingerprint !== currentFingerprint) {
|
|
430
|
+
nextState[modelId] = currentFingerprint;
|
|
431
|
+
stateChanged = true;
|
|
432
|
+
}
|
|
433
|
+
continue;
|
|
385
434
|
}
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
|
|
435
|
+
if (trackedFingerprint && trackedFingerprint === currentFingerprint) {
|
|
436
|
+
const seededModel = applySeedMarker(existingModel);
|
|
437
|
+
doc.models = doc.models.map((entry) => (entry.slug === modelId ? seededModel : entry));
|
|
438
|
+
nextState[modelId] = fingerprintModel(seededModel);
|
|
439
|
+
seeded = true;
|
|
440
|
+
changed = true;
|
|
441
|
+
stateChanged = true;
|
|
442
|
+
continue;
|
|
389
443
|
}
|
|
390
|
-
|
|
444
|
+
if (trackedFingerprint && trackedFingerprint !== currentFingerprint) {
|
|
445
|
+
delete nextState[modelId];
|
|
446
|
+
stateChanged = true;
|
|
447
|
+
}
|
|
448
|
+
continue;
|
|
391
449
|
}
|
|
392
|
-
|
|
393
|
-
|
|
450
|
+
const seed = buildSeedModel(doc.models, modelId);
|
|
451
|
+
doc.models = [seed, ...doc.models];
|
|
452
|
+
nextState[modelId] = fingerprintModel(seed);
|
|
453
|
+
seeded = true;
|
|
454
|
+
changed = true;
|
|
455
|
+
stateChanged = true;
|
|
456
|
+
}
|
|
457
|
+
// Keep The Claw Bay seeds grouped at the top for easy discovery in the picker.
|
|
458
|
+
const bySlug = new Map();
|
|
459
|
+
const rest = [];
|
|
460
|
+
for (const entry of doc.models) {
|
|
461
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
462
|
+
if (slug && TARGET_MODEL_ID_SET.has(slug) && !bySlug.has(slug)) {
|
|
463
|
+
bySlug.set(slug, entry);
|
|
464
|
+
continue;
|
|
394
465
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
466
|
+
rest.push(entry);
|
|
467
|
+
}
|
|
468
|
+
const orderedTargets = [];
|
|
469
|
+
for (const id of TARGET_MODEL_IDS) {
|
|
470
|
+
const entry = bySlug.get(id);
|
|
471
|
+
if (entry)
|
|
472
|
+
orderedTargets.push(entry);
|
|
473
|
+
}
|
|
474
|
+
doc.models = [...orderedTargets, ...rest];
|
|
475
|
+
if (setCacheFreshnessMetadata(doc, clientVersion)) {
|
|
476
|
+
changed = true;
|
|
477
|
+
}
|
|
478
|
+
if (changed) {
|
|
479
|
+
await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
480
|
+
}
|
|
481
|
+
const hasAnyTracked = TARGET_MODEL_IDS.some((id) => Boolean(nextState[id]));
|
|
482
|
+
if (!hasAnyTracked) {
|
|
483
|
+
if (Object.keys(existingState).length > 0) {
|
|
484
|
+
await removeFileIfExists(statePath);
|
|
398
485
|
}
|
|
399
|
-
return {
|
|
400
|
-
action: existingState && existingState.fingerprint === currentFingerprint ? "already_seeded" : "already_present",
|
|
401
|
-
};
|
|
402
486
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
487
|
+
else if (stateChanged || seeded) {
|
|
488
|
+
await writePatchState(statePath, nextState);
|
|
489
|
+
}
|
|
490
|
+
if (seeded)
|
|
491
|
+
return { action: "seeded" };
|
|
492
|
+
if (changed || stateChanged)
|
|
493
|
+
return { action: "refreshed" };
|
|
494
|
+
const allSeeded = TARGET_MODEL_IDS.every((id) => {
|
|
495
|
+
const model = findModel(doc.models, id);
|
|
496
|
+
return Boolean(model && hasSeedMarker(model));
|
|
497
|
+
});
|
|
498
|
+
return { action: allSeeded ? "already_seeded" : "already_present" };
|
|
409
499
|
}
|
|
410
500
|
catch (error) {
|
|
411
501
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -420,7 +510,7 @@ async function cleanupSeededCodexModelCache(params) {
|
|
|
420
510
|
const statePath = node_path_1.default.join(params.codexHome, MODELS_CACHE_STATE_FILE);
|
|
421
511
|
try {
|
|
422
512
|
const state = await readPatchState(statePath);
|
|
423
|
-
if (
|
|
513
|
+
if (Object.keys(state).length === 0) {
|
|
424
514
|
return { action: "none" };
|
|
425
515
|
}
|
|
426
516
|
const parsed = await readJsonIfExists(cachePath);
|
|
@@ -432,27 +522,26 @@ async function cleanupSeededCodexModelCache(params) {
|
|
|
432
522
|
if (!doc) {
|
|
433
523
|
return {
|
|
434
524
|
action: "none",
|
|
435
|
-
warning: "Codex models cache exists but is not valid JSON in the expected format; left GPT-5.4 cache patch state untouched.",
|
|
525
|
+
warning: "Codex models cache exists but is not valid JSON in the expected format; left GPT-5.4 / GPT-5.4 Mini cache patch state untouched.",
|
|
436
526
|
};
|
|
437
527
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
528
|
+
let removed = 0;
|
|
529
|
+
let preserved = 0;
|
|
530
|
+
doc.models = doc.models.filter((entry) => {
|
|
531
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
532
|
+
if (!slug || !TARGET_MODEL_ID_SET.has(slug))
|
|
533
|
+
return true;
|
|
534
|
+
if (!hasSeedMarker(entry)) {
|
|
535
|
+
preserved += 1;
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
removed += 1;
|
|
539
|
+
return false;
|
|
540
|
+
});
|
|
541
|
+
if (removed === 0) {
|
|
452
542
|
await removeFileIfExists(statePath);
|
|
453
|
-
return { action: "
|
|
543
|
+
return { action: preserved > 0 ? "preserved" : "none" };
|
|
454
544
|
}
|
|
455
|
-
doc.models = doc.models.filter((entry) => entry.slug !== TARGET_MODEL_ID);
|
|
456
545
|
await promises_1.default.writeFile(cachePath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
457
546
|
await removeFileIfExists(statePath);
|
|
458
547
|
return { action: "removed" };
|
package/package.json
CHANGED
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
"cachedInputPer1M": 0.25,
|
|
9
9
|
"outputPer1M": 15.0
|
|
10
10
|
},
|
|
11
|
+
{
|
|
12
|
+
"id": "gpt-5.4-mini",
|
|
13
|
+
"label": "GPT-5.4 Mini",
|
|
14
|
+
"note": "Smaller GPT-5.4 variant for quick iterations.",
|
|
15
|
+
"tone": "sea",
|
|
16
|
+
"inputPer1M": 1.25,
|
|
17
|
+
"cachedInputPer1M": 0.125,
|
|
18
|
+
"outputPer1M": 10.0
|
|
19
|
+
},
|
|
11
20
|
{
|
|
12
21
|
"id": "gpt-5.3-codex",
|
|
13
22
|
"label": "GPT-5.3 Codex",
|