minecodex 0.1.20 → 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/features/images/codex-feature.json +5 -0
- package/features/images/src/chatgpt-image-source-page.mjs +207 -0
- package/features/images/src/chatgpt-image-source.mjs +42 -0
- package/features/images/src/http-server.mjs +92 -3
- package/features/images/src/image-library.mjs +171 -4
- package/features/images/web/app.js +35 -13
- package/features/model-slider/codex-feature.json +5 -1
- package/package.json +1 -1
- package/packages/cli/src/chatgpt-launch-coordinator.mjs +27 -1
- package/packages/cli/src/commands.mjs +34 -16
- package/packages/cli/src/install-progress.mjs +56 -0
- package/packages/cli/src/platform.mjs +0 -12
- package/packages/cli/src/runtime-manager.mjs +25 -28
- package/packages/runtime-host/src/codex-runtime.mjs +358 -99
- package/packages/runtime-host/src/feature-registry.mjs +56 -10
- package/packages/runtime-host/src/main.mjs +11 -1
- package/packages/runtime-host/src/model-capabilities.mjs +86 -0
|
@@ -11,9 +11,11 @@ const HOST_ACTIONS = new Set([
|
|
|
11
11
|
"open-detail-tab",
|
|
12
12
|
"open-editor-modal",
|
|
13
13
|
"resolve-file-paths",
|
|
14
|
+
"import-generated-image",
|
|
14
15
|
]);
|
|
15
16
|
|
|
16
17
|
const PAGE_SCRIPT_ENTRY_KINDS = new Set(["page-script"]);
|
|
18
|
+
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
17
19
|
|
|
18
20
|
export const FEATURE_PROTOCOL_VERSION = 1;
|
|
19
21
|
export const FEATURE_HEALTH_TIMEOUT_MS = 750;
|
|
@@ -30,6 +32,37 @@ function normalizeUrl(value, field) {
|
|
|
30
32
|
return url.href;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
function normalizeRuntimeUrls(manifest, env) {
|
|
36
|
+
let surfaceUrl = manifest.surfaceUrl ? normalizeUrl(manifest.surfaceUrl, "surfaceUrl") : null;
|
|
37
|
+
let healthUrl = manifest.healthUrl || surfaceUrl
|
|
38
|
+
? normalizeUrl(manifest.healthUrl ?? surfaceUrl, "healthUrl")
|
|
39
|
+
: null;
|
|
40
|
+
const portEnvironment = manifest.runtimePortEnvironment;
|
|
41
|
+
if (!portEnvironment) return { surfaceUrl, healthUrl };
|
|
42
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(portEnvironment)) {
|
|
43
|
+
throw new Error("runtimePortEnvironment must be an environment variable name");
|
|
44
|
+
}
|
|
45
|
+
if (!surfaceUrl || !healthUrl) {
|
|
46
|
+
throw new Error("runtimePortEnvironment requires surfaceUrl and healthUrl");
|
|
47
|
+
}
|
|
48
|
+
const urls = [new URL(surfaceUrl), new URL(healthUrl)];
|
|
49
|
+
if (urls.some((url) => url.protocol !== "http:" || !LOOPBACK_HOSTS.has(url.hostname))) {
|
|
50
|
+
throw new Error("runtimePortEnvironment may only override loopback http URLs");
|
|
51
|
+
}
|
|
52
|
+
if (urls[0].origin !== urls[1].origin) {
|
|
53
|
+
throw new Error("runtimePortEnvironment requires surfaceUrl and healthUrl to share an origin");
|
|
54
|
+
}
|
|
55
|
+
const configuredPort = env?.[portEnvironment];
|
|
56
|
+
if (configuredPort === undefined || configuredPort === "") return { surfaceUrl, healthUrl };
|
|
57
|
+
const port = Number(configuredPort);
|
|
58
|
+
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
59
|
+
throw new Error(`${portEnvironment} must be an integer from 1 to 65535`);
|
|
60
|
+
}
|
|
61
|
+
for (const url of urls) url.port = String(port);
|
|
62
|
+
[surfaceUrl, healthUrl] = urls.map((url) => url.href);
|
|
63
|
+
return { surfaceUrl, healthUrl };
|
|
64
|
+
}
|
|
65
|
+
|
|
33
66
|
function normalizeLocaleKey(value) {
|
|
34
67
|
const key = String(value).trim();
|
|
35
68
|
if (key.toLowerCase().startsWith("zh")) return "zh-CN";
|
|
@@ -85,6 +118,18 @@ async function normalizeModelSelector(modelSelector, projectDir) {
|
|
|
85
118
|
throw new Error("modelSelector.favoriteStorageKey is required");
|
|
86
119
|
}
|
|
87
120
|
|
|
121
|
+
const reasoningEffortOverrides = {};
|
|
122
|
+
for (const [identity, efforts] of Object.entries(modelSelector.reasoningEffortOverrides ?? {})) {
|
|
123
|
+
const exactIdentity = String(identity).trim().toLowerCase();
|
|
124
|
+
const allowedEfforts = Array.isArray(efforts)
|
|
125
|
+
? efforts.map((effort) => String(effort).trim().toLowerCase()).filter(Boolean)
|
|
126
|
+
: [];
|
|
127
|
+
if (!exactIdentity || !allowedEfforts.length || new Set(allowedEfforts).size !== allowedEfforts.length) {
|
|
128
|
+
throw new Error("modelSelector.reasoningEffortOverrides must map exact identities to unique efforts");
|
|
129
|
+
}
|
|
130
|
+
reasoningEffortOverrides[exactIdentity] = allowedEfforts;
|
|
131
|
+
}
|
|
132
|
+
|
|
88
133
|
const providerAliases = {};
|
|
89
134
|
for (const [provider, label] of Object.entries(modelSelector.providerAliases ?? {})) {
|
|
90
135
|
if (!String(provider).trim() || !String(label).trim()) {
|
|
@@ -118,6 +163,7 @@ async function normalizeModelSelector(modelSelector, projectDir) {
|
|
|
118
163
|
return {
|
|
119
164
|
maxVisibleItems,
|
|
120
165
|
favoriteStorageKey,
|
|
166
|
+
reasoningEffortOverrides,
|
|
121
167
|
providerAliases,
|
|
122
168
|
strings,
|
|
123
169
|
icons: {
|
|
@@ -146,7 +192,7 @@ async function normalizePageScript(manifest, projectDir) {
|
|
|
146
192
|
return { resource, source };
|
|
147
193
|
}
|
|
148
194
|
|
|
149
|
-
async function normalizeFeature(manifest, projectDir) {
|
|
195
|
+
async function normalizeFeature(manifest, projectDir, env) {
|
|
150
196
|
if (manifest.schemaVersion !== 1) throw new Error("schemaVersion must be 1");
|
|
151
197
|
if (!manifest.id || !manifest.label) {
|
|
152
198
|
throw new Error("id and label are required");
|
|
@@ -194,7 +240,11 @@ async function normalizeFeature(manifest, projectDir) {
|
|
|
194
240
|
throw new Error("hostActions contains an unsupported action");
|
|
195
241
|
}
|
|
196
242
|
|
|
197
|
-
const
|
|
243
|
+
const pageScript = manifest.pageScript
|
|
244
|
+
? await normalizePageScript({ ...manifest, entry: { ...(manifest.entry ?? {}), script: manifest.pageScript.resource } }, projectDir)
|
|
245
|
+
: null;
|
|
246
|
+
|
|
247
|
+
const { surfaceUrl, healthUrl } = normalizeRuntimeUrls(manifest, env);
|
|
198
248
|
const entryLabel = normalizeLocalizedText(manifest.entry?.ariaLabel ?? manifest.label, "entry.ariaLabel");
|
|
199
249
|
const summaryLabel = manifest.pinnedSummary
|
|
200
250
|
? normalizeLocalizedText(manifest.pinnedSummary.label ?? manifest.label, "pinnedSummary.label")
|
|
@@ -218,9 +268,7 @@ async function normalizeFeature(manifest, projectDir) {
|
|
|
218
268
|
label: label.value,
|
|
219
269
|
labels: label.locales,
|
|
220
270
|
surfaceUrl,
|
|
221
|
-
healthUrl
|
|
222
|
-
? normalizeUrl(manifest.healthUrl ?? surfaceUrl, "healthUrl")
|
|
223
|
-
: null,
|
|
271
|
+
healthUrl,
|
|
224
272
|
icon: await normalizeIcon(manifest.icon, projectDir),
|
|
225
273
|
entry: {
|
|
226
274
|
kind: entryKind,
|
|
@@ -242,9 +290,7 @@ async function normalizeFeature(manifest, projectDir) {
|
|
|
242
290
|
modelSelector: isModelSelector
|
|
243
291
|
? await normalizeModelSelector(manifest.modelSelector, projectDir)
|
|
244
292
|
: null,
|
|
245
|
-
pageScript: isPageScript
|
|
246
|
-
? await normalizePageScript(manifest, projectDir)
|
|
247
|
-
: null,
|
|
293
|
+
pageScript: isPageScript ? await normalizePageScript(manifest, projectDir) : pageScript,
|
|
248
294
|
placement: {
|
|
249
295
|
after: manifest.placement?.after ?? "sites",
|
|
250
296
|
order: Number(manifest.placement?.order ?? 100),
|
|
@@ -254,7 +300,7 @@ async function normalizeFeature(manifest, projectDir) {
|
|
|
254
300
|
};
|
|
255
301
|
}
|
|
256
302
|
|
|
257
|
-
export async function discoverFeatures(featuresRoot) {
|
|
303
|
+
export async function discoverFeatures(featuresRoot, { env = process.env } = {}) {
|
|
258
304
|
const entries = await readdir(featuresRoot, { withFileTypes: true });
|
|
259
305
|
const features = [];
|
|
260
306
|
|
|
@@ -270,7 +316,7 @@ export async function discoverFeatures(featuresRoot) {
|
|
|
270
316
|
}
|
|
271
317
|
try {
|
|
272
318
|
const manifest = JSON.parse(source);
|
|
273
|
-
features.push(await normalizeFeature(manifest, projectDir));
|
|
319
|
+
features.push(await normalizeFeature(manifest, projectDir, env));
|
|
274
320
|
} catch (error) {
|
|
275
321
|
throw new Error(`Invalid ${entry.name}/${MANIFEST_NAME}: ${error.message}`);
|
|
276
322
|
}
|
|
@@ -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
|
+
}
|