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.
@@ -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 surfaceUrl = manifest.surfaceUrl ? normalizeUrl(manifest.surfaceUrl, "surfaceUrl") : null;
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: manifest.healthUrl || surfaceUrl
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) feature.modelSelector.models = modelCatalog;
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
+ }