minecodex 0.2.1 → 0.2.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/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
4
4
|
import { assertNodeSupported, MacPlatformAdapter } from "./platform.mjs";
|
|
5
5
|
import { ensureConfig, ensureControlToken, readConfig } from "./config.mjs";
|
|
6
6
|
import { createControlServer } from "./control-server.mjs";
|
|
7
|
+
import { createInstallReporter } from "./install-progress.mjs";
|
|
7
8
|
import { createNpmAdapter } from "./npm-adapter.mjs";
|
|
8
9
|
import { resolvePaths } from "./paths.mjs";
|
|
9
10
|
import { RuntimeManager, writeServicePid } from "./runtime-manager.mjs";
|
|
@@ -118,20 +119,37 @@ export async function confirmRestartNow({ input = process.stdin, terminalOutput
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
async function installCommand({ paths, platform, cliPath, output, serviceProbe, fetchImpl, confirmRestart, nodeVersion = process.versions.node }) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const previousService = await serviceSnapshot(platform, paths);
|
|
125
|
-
await ensureConfig(paths.configPath);
|
|
126
|
-
await ensureControlToken(paths.tokenPath);
|
|
122
|
+
const reporter = createInstallReporter({ output });
|
|
123
|
+
reporter.heading("Installing MineCodex");
|
|
124
|
+
let previousService = null;
|
|
127
125
|
try {
|
|
128
|
-
await
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
await reporter.step(1, 6, "Checking your system", async () => {
|
|
127
|
+
assertNodeSupported(nodeVersion);
|
|
128
|
+
platform.assertInstallSupported();
|
|
129
|
+
await platform.assertCodexInstalled();
|
|
130
|
+
});
|
|
131
|
+
await reporter.step(2, 6, "Preparing your settings", async () => {
|
|
132
|
+
await ensureConfig(paths.configPath);
|
|
133
|
+
await ensureControlToken(paths.tokenPath);
|
|
134
|
+
});
|
|
135
|
+
previousService = await reporter.step(3, 6, "Checking your current setup", async () => {
|
|
136
|
+
return serviceSnapshot(platform, paths);
|
|
137
|
+
});
|
|
138
|
+
await reporter.step(4, 6, "Installing the background helper", async () => {
|
|
139
|
+
await platform.installService({ cliPath, paths, start: true });
|
|
140
|
+
});
|
|
141
|
+
await reporter.step(5, 6, "Waiting for the background helper (up to 20 seconds)", async () => {
|
|
142
|
+
const waitForServiceImpl = serviceProbe?.waitForService ?? waitForService;
|
|
143
|
+
if (!(await waitForServiceImpl(DEFAULT_CONTROL_PORT, { fetchImpl }))) {
|
|
144
|
+
throw new Error("MineCodex service did not become ready. Check " + paths.serviceErrorLogPath);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
await reporter.step(6, 6, "Waiting for MineCodex to be ready (up to 30 seconds)", async () => {
|
|
148
|
+
const waitForManagedReadyImpl = serviceProbe?.waitForManagedReady ?? waitForManagedReady;
|
|
149
|
+
if (!(await waitForManagedReadyImpl(paths, { fetchImpl }))) {
|
|
150
|
+
throw new Error("MineCodex service did not become ready. Check " + paths.serviceErrorLogPath);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
135
153
|
} catch (error) {
|
|
136
154
|
let rollbackError = null;
|
|
137
155
|
try {
|
|
@@ -141,12 +159,12 @@ async function installCommand({ paths, platform, cliPath, output, serviceProbe,
|
|
|
141
159
|
}
|
|
142
160
|
throw appendRollbackError(error, rollbackError);
|
|
143
161
|
}
|
|
144
|
-
|
|
162
|
+
reporter.heading("MineCodex installed. It will be enabled the next time Codex restarts.");
|
|
145
163
|
if (await confirmRestart()) {
|
|
146
164
|
await restartCommand({ paths, output, fetchImpl });
|
|
147
165
|
} else {
|
|
148
|
-
|
|
149
|
-
|
|
166
|
+
reporter.heading("Run `mcx restart` when you are ready.");
|
|
167
|
+
}
|
|
150
168
|
}
|
|
151
169
|
|
|
152
170
|
async function openCommand({ paths, platform, serviceProbe, fetchImpl }) {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
2
|
+
|
|
3
|
+
function createInstallReporter({ output = console.log, terminalOutput = process.stdout } = {}) {
|
|
4
|
+
// 只有真实终端才显示 spinner;测试或脚本环境保持普通逐行文本。
|
|
5
|
+
const interactive = output === console.log && Boolean(terminalOutput?.isTTY);
|
|
6
|
+
let timer = null;
|
|
7
|
+
|
|
8
|
+
function stopSpinner() {
|
|
9
|
+
if (timer) clearInterval(timer);
|
|
10
|
+
timer = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function writeLine(line) {
|
|
14
|
+
if (!interactive) {
|
|
15
|
+
output(line);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
terminalOutput.write(`\r\x1b[2K${line}\n`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function startSpinner(line) {
|
|
22
|
+
if (!interactive) {
|
|
23
|
+
output(line);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
let frame = 0;
|
|
27
|
+
terminalOutput.write(`\r\x1b[2K${SPINNER_FRAMES[frame]} ${line}`);
|
|
28
|
+
timer = setInterval(() => {
|
|
29
|
+
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
30
|
+
terminalOutput.write(`\r\x1b[2K${SPINNER_FRAMES[frame]} ${line}`);
|
|
31
|
+
}, 80);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
heading(line) {
|
|
36
|
+
writeLine(line);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async step(index, total, label, action) {
|
|
40
|
+
const prefix = `[${index}/${total}] ${label}`;
|
|
41
|
+
startSpinner(prefix);
|
|
42
|
+
try {
|
|
43
|
+
const result = await action();
|
|
44
|
+
stopSpinner();
|
|
45
|
+
writeLine(`✓ ${prefix}`);
|
|
46
|
+
return result;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
stopSpinner();
|
|
49
|
+
writeLine(`✗ ${prefix}`);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { createInstallReporter };
|
|
@@ -1093,6 +1093,19 @@ export function createInjectionSource(features, {
|
|
|
1093
1093
|
return formatIdentifier(identity.backend).toLowerCase() === nativeLabel ? native : thirdParty;
|
|
1094
1094
|
}
|
|
1095
1095
|
|
|
1096
|
+
function visibleReasoningLevelsFor(model) {
|
|
1097
|
+
if (!model) return [];
|
|
1098
|
+
const capability = model.modelSelectorCapability;
|
|
1099
|
+
if (capability && Array.isArray(capability.levels)) {
|
|
1100
|
+
const allowed = new Set(capability.levels);
|
|
1101
|
+
return (model.supportedReasoningLevels ?? []).filter((level) => {
|
|
1102
|
+
const effort = String(level?.effort ?? level ?? "").toLowerCase();
|
|
1103
|
+
return allowed.has(effort);
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
return model.supportedReasoningLevels ?? [];
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1096
1109
|
function loadModelFavorites(selector) {
|
|
1097
1110
|
try {
|
|
1098
1111
|
const value = JSON.parse(localStorage.getItem(selector.favoriteStorageKey) ?? "[]");
|
|
@@ -2106,7 +2119,13 @@ export function createInjectionSource(features, {
|
|
|
2106
2119
|
function enhanceGenericModelPicker(parentMenu, selector) {
|
|
2107
2120
|
const identity = modelIdentity(nativeModelValue(parentMenu));
|
|
2108
2121
|
const model = modelDefinitionFor(identity, selector);
|
|
2109
|
-
const
|
|
2122
|
+
const catalogModel = catalogModelFor(identity, selector);
|
|
2123
|
+
const modelWithCapability = model?.modelSelectorCapability ? model : {
|
|
2124
|
+
...(model ?? {}),
|
|
2125
|
+
...(catalogModel ?? {}),
|
|
2126
|
+
supportedReasoningLevels: model?.supportedReasoningLevels ?? catalogModel?.supportedReasoningLevels ?? [],
|
|
2127
|
+
};
|
|
2128
|
+
const efforts = visibleReasoningLevelsFor(modelWithCapability);
|
|
2110
2129
|
if (!model || efforts.length < 2) return false;
|
|
2111
2130
|
// 控制器推导的任务内实际 effort 优先,避免子任务/主任务切换后触发器属性滞后;
|
|
2112
2131
|
// 触发器属性仅在控制器无法解析时作为实时回退,静态目录默认值最后兜底。
|
|
@@ -2116,9 +2135,19 @@ export function createInjectionSource(features, {
|
|
|
2116
2135
|
?.getAttribute("data-selected-reasoning-effort")
|
|
2117
2136
|
?? model.defaultReasoningLevel
|
|
2118
2137
|
?? efforts[0].effort;
|
|
2138
|
+
const effortRank = (value) => {
|
|
2139
|
+
const order = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
|
|
2140
|
+
const index = order.indexOf(String(value ?? "").toLowerCase());
|
|
2141
|
+
return index < 0 ? -1 : index;
|
|
2142
|
+
};
|
|
2143
|
+
const visibleEffort = efforts.find((level) => level.effort === selectedEffort)?.effort
|
|
2144
|
+
?? efforts.filter((level) => effortRank(level.effort) <= effortRank(selectedEffort))
|
|
2145
|
+
.sort((left, right) => effortRank(right.effort) - effortRank(left.effort))[0]?.effort
|
|
2146
|
+
?? efforts[0].effort;
|
|
2147
|
+
const selectedEffortValue = visibleEffort;
|
|
2119
2148
|
const selectedServiceTier = nativeComposerModelController()?.selectedServiceTier;
|
|
2120
2149
|
const serviceTierKey = selectedServiceTier?.id ?? selectedServiceTier ?? "standard";
|
|
2121
|
-
const key = `${identity.raw}:${
|
|
2150
|
+
const key = `${identity.raw}:${selectedEffortValue}:${serviceTierKey}:${efforts.map((level) => level.effort).join(",")}`;
|
|
2122
2151
|
const existing = parentMenu.querySelector(":scope > [data-codex-model-slider-generic]");
|
|
2123
2152
|
const nativeTemplateReady = Boolean(
|
|
2124
2153
|
parentMenu.firstElementChild
|
|
@@ -2140,7 +2169,7 @@ export function createInjectionSource(features, {
|
|
|
2140
2169
|
existing?.remove();
|
|
2141
2170
|
const nativeContainer = parentMenu.firstElementChild;
|
|
2142
2171
|
if (!nativeContainer) return false;
|
|
2143
|
-
const shell = createGenericModelSlider(parentMenu, selector, identity, model,
|
|
2172
|
+
const shell = createGenericModelSlider(parentMenu, selector, identity, model, selectedEffortValue);
|
|
2144
2173
|
if (!shell) return false;
|
|
2145
2174
|
if (!modelSelectorGenericContainers.has(nativeContainer)) {
|
|
2146
2175
|
modelSelectorGenericContainers.set(nativeContainer, nativeContainer.style.display);
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
selectEnabledFeatures,
|
|
12
12
|
startFeatureProcesses,
|
|
13
13
|
} from "./feature-registry.mjs";
|
|
14
|
+
import { applyVisibleReasoningLevels, loadModelCapabilities } from "./model-capabilities.mjs";
|
|
14
15
|
|
|
15
16
|
const runtimeRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
16
17
|
const featuresRoot = process.env.CODEX_FEATURES_ROOT ?? path.dirname(runtimeRoot);
|
|
@@ -22,8 +23,17 @@ const discoveredFeatures = await discoverFeatures(featuresRoot);
|
|
|
22
23
|
if (!discoveredFeatures.length) throw new Error(`No codex-feature.json files found under ${featuresRoot}`);
|
|
23
24
|
const features = selectEnabledFeatures(discoveredFeatures, process.env.MINECODEX_ENABLED_FEATURES);
|
|
24
25
|
const modelCatalog = await loadConfiguredModelCatalog();
|
|
26
|
+
const modelCapabilities = await loadModelCapabilities();
|
|
25
27
|
for (const feature of features) {
|
|
26
|
-
if (feature.modelSelector)
|
|
28
|
+
if (feature.modelSelector) {
|
|
29
|
+
feature.modelSelector.models = modelCatalog.map((model) => (
|
|
30
|
+
applyVisibleReasoningLevels(
|
|
31
|
+
model,
|
|
32
|
+
modelCapabilities.get(String(model.slug ?? "").toLowerCase()),
|
|
33
|
+
model.supportedReasoningLevels,
|
|
34
|
+
)
|
|
35
|
+
));
|
|
36
|
+
}
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
let readyWritePromise = Promise.resolve();
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
const CODEX_REASONING_ORDER = ["low", "medium", "high", "xhigh", "max", "ultra"];
|
|
6
|
+
|
|
7
|
+
function uniqueLevels(values) {
|
|
8
|
+
return [...new Set(values.filter(Boolean))];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
function normalizedEfforts(entry) {
|
|
13
|
+
if (!Array.isArray(entry)) return null;
|
|
14
|
+
const levels = entry.map(String).filter((value) => (
|
|
15
|
+
CODEX_REASONING_ORDER.includes(value) || value === "none" || value === "minimal"
|
|
16
|
+
));
|
|
17
|
+
return levels.length ? uniqueLevels(levels) : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveModelRecord(records, modelId) {
|
|
21
|
+
if (!records || typeof records !== "object") return undefined;
|
|
22
|
+
if (Object.prototype.hasOwnProperty.call(records, modelId)) return records[modelId];
|
|
23
|
+
const folded = modelId.toLowerCase();
|
|
24
|
+
for (const [key, value] of Object.entries(records)) {
|
|
25
|
+
if (key.toLowerCase() === folded) return value;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function mergeCapability(providerConfig, modelId) {
|
|
31
|
+
if (!providerConfig) return null;
|
|
32
|
+
const configuredEfforts = normalizedEfforts(
|
|
33
|
+
resolveModelRecord(providerConfig.modelReasoningEfforts, modelId),
|
|
34
|
+
);
|
|
35
|
+
if (configuredEfforts) {
|
|
36
|
+
return {
|
|
37
|
+
source: "opencodex",
|
|
38
|
+
kind: "effort",
|
|
39
|
+
levels: uniqueLevels(configuredEfforts),
|
|
40
|
+
map: resolveModelRecord(providerConfig.modelReasoningEffortMap, modelId) ?? null,
|
|
41
|
+
rawEfforts: configuredEfforts,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(providerConfig.noReasoningModels) && providerConfig.noReasoningModels.includes(modelId)) {
|
|
45
|
+
return { source: "opencodex", kind: "no-effort", levels: null, map: null, rawEfforts: [] };
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function loadModelCapabilities({
|
|
51
|
+
opencodexConfigPath = process.env.OPENCODEX_CONFIG_PATH ?? path.join(os.homedir(), ".opencodex", "config.json"),
|
|
52
|
+
read = readFile,
|
|
53
|
+
} = {}) {
|
|
54
|
+
const configPayload = await read(opencodexConfigPath, "utf8").then((value) => JSON.parse(value)).catch(() => null);
|
|
55
|
+
const providers = configPayload?.providers ?? {};
|
|
56
|
+
const result = new Map();
|
|
57
|
+
for (const [providerId, providerConfig] of Object.entries(providers)) {
|
|
58
|
+
const modelIds = new Set([
|
|
59
|
+
...Object.keys(providerConfig?.modelReasoningEfforts ?? {}),
|
|
60
|
+
...Object.keys(providerConfig?.modelReasoningEffortMap ?? {}),
|
|
61
|
+
...Object.keys(providerConfig?.thinkingToggleModels ?? {}),
|
|
62
|
+
...Object.keys(providerConfig?.thinkingBudgetModels ?? {}),
|
|
63
|
+
...(Array.isArray(providerConfig?.noReasoningModels) ? providerConfig.noReasoningModels : []),
|
|
64
|
+
]);
|
|
65
|
+
for (const modelId of modelIds) {
|
|
66
|
+
const capability = mergeCapability(providerConfig, modelId);
|
|
67
|
+
if (capability) result.set(`${providerId}/${modelId}`.toLowerCase(), capability);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function applyVisibleReasoningLevels(catalogModel, capability, fallbackLevels) {
|
|
74
|
+
if (!catalogModel) return catalogModel;
|
|
75
|
+
const rawLevels = Array.isArray(fallbackLevels) ? fallbackLevels : (catalogModel.supportedReasoningLevels ?? []);
|
|
76
|
+
if (!Array.isArray(rawLevels)) return catalogModel;
|
|
77
|
+
if (capability && Array.isArray(capability.levels)) {
|
|
78
|
+
const allowed = new Set(capability.levels);
|
|
79
|
+
const visible = rawLevels.filter((level) => {
|
|
80
|
+
const effort = String(level?.effort ?? level ?? "").toLowerCase();
|
|
81
|
+
return allowed.has(effort);
|
|
82
|
+
});
|
|
83
|
+
return { ...catalogModel, supportedReasoningLevels: visible, modelSelectorCapability: capability };
|
|
84
|
+
}
|
|
85
|
+
return { ...catalogModel, modelSelectorCapability: capability ?? null };
|
|
86
|
+
}
|