minecodex 1.0.7 → 1.0.82
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/model-slider/codex-feature.json +0 -7
- package/package.json +1 -1
- package/packages/runtime-host/src/codex-efforts.mjs +47 -0
- package/packages/runtime-host/src/codex-runtime.mjs +55 -55
- package/packages/runtime-host/src/feature-registry.mjs +1 -13
- package/packages/runtime-host/src/main.mjs +7 -9
- package/packages/runtime-host/src/model-capabilities.mjs +0 -86
|
@@ -16,13 +16,6 @@
|
|
|
16
16
|
"modelSelector": {
|
|
17
17
|
"maxVisibleItems": 6,
|
|
18
18
|
"favoriteStorageKey": "codex-model-slider:favorites:v1",
|
|
19
|
-
"reasoningEffortOverrides": {
|
|
20
|
-
"command-code/Qwen-Qwen3.8-Flash": ["low", "medium", "xhigh"],
|
|
21
|
-
"command-code/xai-grok-4.6": ["low", "medium", "high", "xhigh"],
|
|
22
|
-
"command-code/xiaomi-mimo-v2.5": ["low", "medium", "high", "xhigh"],
|
|
23
|
-
"opencode-go/hy3": ["low", "medium", "high"],
|
|
24
|
-
"opencode-go/muse-spark-1.2-contributor": ["low", "medium", "high", "xhigh"]
|
|
25
|
-
},
|
|
26
19
|
"providerAliases": {
|
|
27
20
|
"opencode-go": "OpenCode Go",
|
|
28
21
|
"packyapi": "PackyAPI",
|
package/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
// Model Slider 思考强度来源说明(为什么需要这个模块)
|
|
6
|
+
//
|
|
7
|
+
// OpenCodex 把第三方模型路由给 Codex 时,会在 catalog 里给所有“有思考强度”的
|
|
8
|
+
// 模型额外广告 mock 的 max/ultra(即使上游并不支持)。GLM 之类被 OpenCodex 测得
|
|
9
|
+
// 准的模型没有这个问题;而像 Qwen/Grok/MiMo 这类 OpenCodex 没内置档位的模型,
|
|
10
|
+
// 只能通过用户的 OpenCodex 配置声明真实档位。
|
|
11
|
+
//
|
|
12
|
+
// 这个模块只做一件事:读取 OpenCodex 配置(~/.opencodex/config.json)中按
|
|
13
|
+
// provider 声明的 modelReasoningEfforts,作为 Model Slider 的“真实档位表”。
|
|
14
|
+
// 它不产生、不猜测、不修改任何档位;配置里没有的模型保持原样。
|
|
15
|
+
function canonicalEfforts(values) {
|
|
16
|
+
if (!Array.isArray(values)) return null;
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
const out = values
|
|
19
|
+
.map((value) => String(value).trim().toLowerCase())
|
|
20
|
+
.filter((value) => {
|
|
21
|
+
if (!value || seen.has(value)) return false;
|
|
22
|
+
seen.add(value);
|
|
23
|
+
return true;
|
|
24
|
+
});
|
|
25
|
+
return out.length ? out : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 读取 OpenCodex 配置中按 provider 声明的真实思考强度档位。OpenCodex 同步 catalog
|
|
29
|
+
// 时会额外广告 mock 的 max/ultra;这里只透传 provider 自己声明的档位,供 Model
|
|
30
|
+
// Slider 精确显示,避免 UI 出现上游并不支持的档位。
|
|
31
|
+
export async function loadOpenCodexRealEfforts({
|
|
32
|
+
opencodexConfigPath = process.env.OPENCODEX_CONFIG_PATH ?? path.join(os.homedir(), ".opencodex", "config.json"),
|
|
33
|
+
read = readFile,
|
|
34
|
+
} = {}) {
|
|
35
|
+
const configPayload = await read(opencodexConfigPath, "utf8")
|
|
36
|
+
.then((value) => JSON.parse(value))
|
|
37
|
+
.catch(() => null);
|
|
38
|
+
const entries = {};
|
|
39
|
+
for (const [provider, providerConfig] of Object.entries(configPayload?.providers ?? {})) {
|
|
40
|
+
for (const [modelId, efforts] of Object.entries(providerConfig?.modelReasoningEfforts ?? {})) {
|
|
41
|
+
const levels = canonicalEfforts(efforts);
|
|
42
|
+
if (!levels) continue;
|
|
43
|
+
entries[`${provider}/${modelId}`.toLowerCase()] = levels;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return entries;
|
|
47
|
+
}
|
|
@@ -380,15 +380,11 @@ export function createInjectionSource(features, {
|
|
|
380
380
|
[data-codex-model-slider-trigger] [class*="_ModelPickerTriggerEffortLabel_"] {
|
|
381
381
|
display: inline-flex; align-items: center; align-self: center;
|
|
382
382
|
}
|
|
383
|
-
/*
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
[data-codex-model-slider-has-fast="false"] [data-codex-model-slider-model-button] > span {
|
|
389
|
-
width: 100%; min-width: 0;
|
|
390
|
-
}
|
|
391
|
-
[data-codex-model-slider-has-fast="false"] > :has(> [data-codex-model-slider-model-button])::before {
|
|
383
|
+
/* 模型按钮 wrapper 保持内容 hug,由原生 ViewControls 的
|
|
384
|
+
justify-content:center 相对面板全宽居中。原生 ::before 占位
|
|
385
|
+
(约 16px)会把 hug 内容推离中心,统一隐藏,让按钮中心与面板
|
|
386
|
+
中心重合;Fast 按钮本身绝对定位于左缘不受影响。 */
|
|
387
|
+
[data-codex-model-slider-generic] > :has(> [data-codex-model-slider-model-button])::before {
|
|
392
388
|
display: none !important; content: none !important;
|
|
393
389
|
}
|
|
394
390
|
html[data-codex-model-slider-selecting-effort] [role="menu"][data-state="open"]:not(:has([data-reasoning-slider])) {
|
|
@@ -856,7 +852,10 @@ export function createInjectionSource(features, {
|
|
|
856
852
|
async function loadNativeFastIconFromBundle() {
|
|
857
853
|
// 两个图标均已就绪(持久化或 DOM 克隆)时不再拉取整包 app-initial 资源。
|
|
858
854
|
if (modelSelectorNativeFastIcons?.active && modelSelectorNativeFastIcons?.inactive) return;
|
|
859
|
-
|
|
855
|
+
// Fast bolt 图标定义在新版随 app-primary 入口包加载(app-initial 里没有),
|
|
856
|
+
// 按 app-primary -> app-initial 顺序取第一个已加载的入口包。
|
|
857
|
+
const sourceLink = document.querySelector('link[href*="/assets/app-primary-"][href$=".js"]')
|
|
858
|
+
?? document.querySelector('link[href*="/assets/app-initial-"][href$=".js"]');
|
|
860
859
|
if (!sourceLink?.href) return;
|
|
861
860
|
try {
|
|
862
861
|
const source = await fetch(sourceLink.href).then((response) => response.text());
|
|
@@ -876,12 +875,14 @@ export function createInjectionSource(features, {
|
|
|
876
875
|
: `<path d="${path}" fill="currentColor" />`,
|
|
877
876
|
}, { brand: true });
|
|
878
877
|
if (!svg) return;
|
|
878
|
+
svg.setAttribute("width", "20");
|
|
879
|
+
svg.setAttribute("height", "20");
|
|
879
880
|
modelSelectorNativeFastIcons[state] = svg;
|
|
880
881
|
modelSelectorNativeFastIconSources[state] = "bundle";
|
|
881
882
|
gained = true;
|
|
882
883
|
};
|
|
883
884
|
define("active", "M11\\.9125 21\\.4125", { viewBox: "0 0 24 24" });
|
|
884
|
-
define("inactive", "
|
|
885
|
+
define("inactive", "M9\\.80999 17\\.8302", { viewBox: "0 0 20 20" });
|
|
885
886
|
if (gained) {
|
|
886
887
|
modelSelectorNativeFastIconsDirty = true;
|
|
887
888
|
persistNativeFastIcons();
|
|
@@ -920,8 +921,11 @@ export function createInjectionSource(features, {
|
|
|
920
921
|
if (!svg) return;
|
|
921
922
|
const template = svg.cloneNode(true);
|
|
922
923
|
template.removeAttribute("id");
|
|
923
|
-
|
|
924
|
-
|
|
924
|
+
// 统一呈现尺寸:保留原生 viewBox,固定 20px。DOM 克隆来源是
|
|
925
|
+
// 原生 active(24px)/inactive(20px) 两套 SVG,若不归一化,
|
|
926
|
+
// active 24px 放进 26px 的 Fast content 容器会显得大一圈。
|
|
927
|
+
template.setAttribute("width", "20");
|
|
928
|
+
template.setAttribute("height", "20");
|
|
925
929
|
if (!modelSelectorNativeFastIcons) {
|
|
926
930
|
modelSelectorNativeFastIcons = {};
|
|
927
931
|
modelSelectorNativeFastIconSources = {};
|
|
@@ -931,7 +935,9 @@ export function createInjectionSource(features, {
|
|
|
931
935
|
modelSelectorNativeFastIconsDirty = true;
|
|
932
936
|
};
|
|
933
937
|
capture("active", "M11.9125 21.4125");
|
|
934
|
-
|
|
938
|
+
// 现版 app 的 inactive bolt 为 20x20,路径以 M9.80999 开头;
|
|
939
|
+
// 旧的 M7.38 前缀已匹配不到任何图标,导致 inactive 缺失。
|
|
940
|
+
capture("inactive", "M9.80999 17.8302");
|
|
935
941
|
}
|
|
936
942
|
const ready = Boolean(
|
|
937
943
|
modelSelectorNativeFastIcons?.active && modelSelectorNativeFastIcons?.inactive,
|
|
@@ -960,6 +966,10 @@ export function createInjectionSource(features, {
|
|
|
960
966
|
if (!template) return null;
|
|
961
967
|
const icon = template.cloneNode(true);
|
|
962
968
|
icon.setAttribute("aria-hidden", "true");
|
|
969
|
+
// 出口统一规格化:无论缓存来自 DOM 克隆(24/20px)、bundle(20px)还是
|
|
970
|
+
// 旧版持久化数据,最终都以 20px 呈现,与原生 Fast bolt 视觉一致。
|
|
971
|
+
icon.setAttribute("width", "20");
|
|
972
|
+
icon.setAttribute("height", "20");
|
|
963
973
|
const iconClass = nativeCssModuleClass("FastModeIcon");
|
|
964
974
|
if (iconClass && !icon.classList.contains(iconClass)) icon.classList.add(iconClass);
|
|
965
975
|
return icon;
|
|
@@ -1109,12 +1119,7 @@ export function createInjectionSource(features, {
|
|
|
1109
1119
|
const nativeReasoningLevels = (model.supportedReasoningEfforts ?? []).map((level) => ({
|
|
1110
1120
|
effort: String(level?.reasoningEffort ?? level?.effort ?? level ?? "").toLowerCase(),
|
|
1111
1121
|
})).filter((level) => level.effort);
|
|
1112
|
-
const
|
|
1113
|
-
const supportedReasoningLevels = allowed
|
|
1114
|
-
? (nativeReasoningLevels.length
|
|
1115
|
-
? nativeReasoningLevels.filter((level) => allowed.includes(level.effort))
|
|
1116
|
-
: allowed.map((effort) => ({ effort })))
|
|
1117
|
-
: nativeReasoningLevels;
|
|
1122
|
+
const supportedReasoningLevels = nativeReasoningLevels;
|
|
1118
1123
|
const currentReasoningLevel = String(controller?.reasoningEffort ?? "").toLowerCase();
|
|
1119
1124
|
return {
|
|
1120
1125
|
slug: String(model.model ?? ""),
|
|
@@ -1134,6 +1139,31 @@ export function createInjectionSource(features, {
|
|
|
1134
1139
|
return nativeCatalogModelFor(identity) ?? catalogModelFor(identity, selector);
|
|
1135
1140
|
}
|
|
1136
1141
|
|
|
1142
|
+
// 用 OpenCodex 配置声明的真实档位对滑条做精确交集,过滤 catalog 为支持
|
|
1143
|
+
// mock 顶档而额外广告的 max/ultra。键匹配顺序:模型自带的
|
|
1144
|
+
// opencodex_capability_provenance → catalog 模型 → 菜单原始 identity;
|
|
1145
|
+
// 只做过滤不扩展,配置未声明的模型保持 catalog 原样。
|
|
1146
|
+
function modelWithOpenCodexEfforts(identity, selector, model) {
|
|
1147
|
+
if (!model || !selector?.openCodexRealEfforts) return model;
|
|
1148
|
+
const provenance = model.opencodex_capability_provenance;
|
|
1149
|
+
const catalogModel = provenance?.provider && provenance?.model_id
|
|
1150
|
+
? null
|
|
1151
|
+
: catalogModelFor(identity, selector);
|
|
1152
|
+
const source = catalogModel ?? model;
|
|
1153
|
+
const sourceProvenance = source.opencodex_capability_provenance;
|
|
1154
|
+
const key = sourceProvenance?.provider && sourceProvenance?.model_id
|
|
1155
|
+
? `${sourceProvenance.provider}/${sourceProvenance.model_id}`.toLowerCase()
|
|
1156
|
+
: identity.raw.toLowerCase();
|
|
1157
|
+
const realEfforts = selector.openCodexRealEfforts[key];
|
|
1158
|
+
if (!realEfforts) return model;
|
|
1159
|
+
const allowed = new Set(realEfforts);
|
|
1160
|
+
const levels = (model.supportedReasoningLevels ?? []).filter((level) => {
|
|
1161
|
+
const effort = String(level?.effort ?? level ?? "").toLowerCase();
|
|
1162
|
+
return allowed.has(effort);
|
|
1163
|
+
});
|
|
1164
|
+
return { ...model, supportedReasoningLevels: levels };
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1137
1167
|
function nativeModelDescriptorForItem(item, observedRaw) {
|
|
1138
1168
|
const target = comparableModelLabel(observedRaw);
|
|
1139
1169
|
if (!target) return null;
|
|
@@ -1184,19 +1214,6 @@ export function createInjectionSource(features, {
|
|
|
1184
1214
|
return formatIdentifier(identity.backend).toLowerCase() === nativeLabel ? native : thirdParty;
|
|
1185
1215
|
}
|
|
1186
1216
|
|
|
1187
|
-
function visibleReasoningLevelsFor(model) {
|
|
1188
|
-
if (!model) return [];
|
|
1189
|
-
const capability = model.modelSelectorCapability;
|
|
1190
|
-
if (capability && Array.isArray(capability.levels)) {
|
|
1191
|
-
const allowed = new Set(capability.levels);
|
|
1192
|
-
return (model.supportedReasoningLevels ?? []).filter((level) => {
|
|
1193
|
-
const effort = String(level?.effort ?? level ?? "").toLowerCase();
|
|
1194
|
-
return allowed.has(effort);
|
|
1195
|
-
});
|
|
1196
|
-
}
|
|
1197
|
-
return model.supportedReasoningLevels ?? [];
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
1217
|
function loadModelFavorites(selector) {
|
|
1201
1218
|
try {
|
|
1202
1219
|
const value = JSON.parse(localStorage.getItem(selector.favoriteStorageKey) ?? "[]");
|
|
@@ -1483,8 +1500,8 @@ export function createInjectionSource(features, {
|
|
|
1483
1500
|
|
|
1484
1501
|
function nativeGptSortRank(rawValue) {
|
|
1485
1502
|
const known = new Map([
|
|
1486
|
-
["gpt-5.6-sol",
|
|
1487
|
-
["gpt-5.5",
|
|
1503
|
+
["gpt-6-astra", 1], ["gpt-5.6-sol", 2], ["gpt-5.6-terra", 3], ["gpt-5.6-luna", 4],
|
|
1504
|
+
["gpt-5.5", 5], ["gpt-5.4", 6], ["gpt-5.4-mini", 7], ["gpt-5.3-codex-spark", 8],
|
|
1488
1505
|
]);
|
|
1489
1506
|
const raw = String(rawValue ?? "").trim();
|
|
1490
1507
|
const knownRank = known.get(raw);
|
|
@@ -2412,23 +2429,8 @@ export function createInjectionSource(features, {
|
|
|
2412
2429
|
function enhanceGenericModelPicker(parentMenu, selector) {
|
|
2413
2430
|
const identity = modelIdentity(nativeModelValue(parentMenu));
|
|
2414
2431
|
const model = modelDefinitionFor(identity, selector);
|
|
2415
|
-
const
|
|
2416
|
-
const
|
|
2417
|
-
// 外部模型在菜单中的 identity 常是 displayName(如 commandcode-auth/xai-grok-4.6),
|
|
2418
|
-
// 而 reasoningEffortOverrides 按 catalog slug(command-code/xai-grok-4.6)声明;
|
|
2419
|
-
// 先按 catalog slug 匹配,再退回菜单原始值,确保档位兜底能命中。
|
|
2420
|
-
const overrideLevels = selector?.reasoningEffortOverrides?.[catalogModel?.slug.toLowerCase()]
|
|
2421
|
-
?? selector?.reasoningEffortOverrides?.[identity.raw.toLowerCase()];
|
|
2422
|
-
const modelWithCapability = model?.modelSelectorCapability ? model : {
|
|
2423
|
-
...(model ?? {}),
|
|
2424
|
-
...(catalogModel ?? {}),
|
|
2425
|
-
// 外部模型 catalog 的 supported_reasoning_levels 可能为空;此时用
|
|
2426
|
-
// modelSelector.reasoningEffortOverrides 补齐档位,否则滑条没有刻度。
|
|
2427
|
-
supportedReasoningLevels: catalogedLevels.length
|
|
2428
|
-
? catalogedLevels
|
|
2429
|
-
: (overrideLevels?.map((effort) => ({ effort })) ?? []),
|
|
2430
|
-
};
|
|
2431
|
-
const efforts = visibleReasoningLevelsFor(modelWithCapability);
|
|
2432
|
+
const displayModel = modelWithOpenCodexEfforts(identity, selector, model);
|
|
2433
|
+
const efforts = displayModel?.supportedReasoningLevels ?? [];
|
|
2432
2434
|
if (!model) return false;
|
|
2433
2435
|
// 控制器推导的任务内实际 effort 优先,避免子任务/主任务切换后触发器属性滞后;
|
|
2434
2436
|
// 触发器属性仅在控制器无法解析时作为实时回退,静态目录默认值最后兜底。
|
|
@@ -2477,13 +2479,11 @@ export function createInjectionSource(features, {
|
|
|
2477
2479
|
existing?.remove();
|
|
2478
2480
|
const nativeContainer = parentMenu.firstElementChild;
|
|
2479
2481
|
if (!nativeContainer) return false;
|
|
2480
|
-
// 使用聚合了 override 兜底档位的 modelWithCapability,空目录档位的外部
|
|
2481
|
-
// 模型才能渲染滑条刻度;原始 model 只负责身份/默认值。
|
|
2482
2482
|
const shell = createGenericModelSlider(
|
|
2483
2483
|
parentMenu,
|
|
2484
2484
|
selector,
|
|
2485
2485
|
identity,
|
|
2486
|
-
|
|
2486
|
+
displayModel,
|
|
2487
2487
|
selectedEffortValue,
|
|
2488
2488
|
);
|
|
2489
2489
|
if (!shell) return false;
|
|
@@ -120,18 +120,6 @@ async function normalizeModelSelector(modelSelector, projectDir) {
|
|
|
120
120
|
throw new Error("modelSelector.favoriteStorageKey is required");
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
const reasoningEffortOverrides = {};
|
|
124
|
-
for (const [identity, efforts] of Object.entries(modelSelector.reasoningEffortOverrides ?? {})) {
|
|
125
|
-
const exactIdentity = String(identity).trim().toLowerCase();
|
|
126
|
-
const allowedEfforts = Array.isArray(efforts)
|
|
127
|
-
? efforts.map((effort) => String(effort).trim().toLowerCase()).filter(Boolean)
|
|
128
|
-
: [];
|
|
129
|
-
if (!exactIdentity || !allowedEfforts.length || new Set(allowedEfforts).size !== allowedEfforts.length) {
|
|
130
|
-
throw new Error("modelSelector.reasoningEffortOverrides must map exact identities to unique efforts");
|
|
131
|
-
}
|
|
132
|
-
reasoningEffortOverrides[exactIdentity] = allowedEfforts;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
123
|
const providerAliases = {};
|
|
136
124
|
for (const [provider, label] of Object.entries(modelSelector.providerAliases ?? {})) {
|
|
137
125
|
if (!String(provider).trim() || !String(label).trim()) {
|
|
@@ -165,7 +153,6 @@ async function normalizeModelSelector(modelSelector, projectDir) {
|
|
|
165
153
|
return {
|
|
166
154
|
maxVisibleItems,
|
|
167
155
|
favoriteStorageKey,
|
|
168
|
-
reasoningEffortOverrides,
|
|
169
156
|
providerAliases,
|
|
170
157
|
strings,
|
|
171
158
|
icons: {
|
|
@@ -367,6 +354,7 @@ export async function loadConfiguredModelCatalog(
|
|
|
367
354
|
].every((value) => typeof value === "string" && value.trim())
|
|
368
355
|
? `${model.opencodex_capability_provenance.provider.trim()}/${model.opencodex_capability_provenance.model_id.trim()}`
|
|
369
356
|
: null,
|
|
357
|
+
opencodex_capability_provenance: model.opencodex_capability_provenance ?? null,
|
|
370
358
|
})).filter((model) => model.slug);
|
|
371
359
|
} catch (error) {
|
|
372
360
|
if (error.code === "ENOENT") return [];
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
selectEnabledFeatures,
|
|
12
12
|
startFeatureProcesses,
|
|
13
13
|
} from "./feature-registry.mjs";
|
|
14
|
-
import {
|
|
14
|
+
import { loadOpenCodexRealEfforts } from "./codex-efforts.mjs";
|
|
15
15
|
|
|
16
16
|
const runtimeRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
17
17
|
const featuresRoot = process.env.CODEX_FEATURES_ROOT ?? path.dirname(runtimeRoot);
|
|
@@ -23,16 +23,14 @@ const discoveredFeatures = await discoverFeatures(featuresRoot);
|
|
|
23
23
|
if (!discoveredFeatures.length) throw new Error(`No codex-feature.json files found under ${featuresRoot}`);
|
|
24
24
|
const features = selectEnabledFeatures(discoveredFeatures, process.env.MINECODEX_ENABLED_FEATURES);
|
|
25
25
|
const modelCatalog = await loadConfiguredModelCatalog();
|
|
26
|
-
|
|
26
|
+
// 以 OpenCodex 配置声明的真实档位为准,避免 catalog 里 mock 出的 max/ultra
|
|
27
|
+
// 出现在 Model Slider 中。后续新增模型无需改这里:在 OpenCodex 配置的
|
|
28
|
+
// providers.<id>.modelReasoningEfforts 里补一行即可,启动后自动生效。
|
|
29
|
+
const openCodexRealEfforts = await loadOpenCodexRealEfforts();
|
|
27
30
|
for (const feature of features) {
|
|
28
31
|
if (feature.modelSelector) {
|
|
29
|
-
feature.modelSelector.models = modelCatalog
|
|
30
|
-
|
|
31
|
-
model,
|
|
32
|
-
modelCapabilities.get(String(model.capabilityKey ?? model.slug ?? "").toLowerCase()),
|
|
33
|
-
model.supportedReasoningLevels,
|
|
34
|
-
)
|
|
35
|
-
));
|
|
32
|
+
feature.modelSelector.models = modelCatalog;
|
|
33
|
+
feature.modelSelector.openCodexRealEfforts = openCodexRealEfforts;
|
|
36
34
|
}
|
|
37
35
|
}
|
|
38
36
|
|
|
@@ -1,86 +0,0 @@
|
|
|
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
|
-
}
|