minecodex 0.2.2 → 1.0.1
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 +2 -0
- package/features/hide-upsell-banner/src/hide-upsell-banner.mjs +2 -1
- package/features/images/codex-feature.json +2 -2
- package/features/images/src/http-server.mjs +67 -1
- package/features/images/src/prompt-index.mjs +45 -10
- package/features/images/web/app.js +555 -0
- package/features/images/web/index.html +16 -1
- package/features/images/web/styles.css +80 -0
- package/package.json +1 -1
- package/packages/cli/src/commands.mjs +117 -73
- package/packages/cli/src/{install-progress.mjs → progress.mjs} +2 -2
- package/packages/runtime-host/src/codex-runtime.mjs +318 -21
- package/packages/runtime-host/src/feature-registry.mjs +7 -0
- package/packages/runtime-host/src/main.mjs +1 -1
|
@@ -165,7 +165,11 @@ export function createInjectionSource(features, {
|
|
|
165
165
|
"--color-token-bg-primary",
|
|
166
166
|
"--color-token-bg-secondary",
|
|
167
167
|
"--color-token-bg-tertiary",
|
|
168
|
+
"--color-surface-tertiary",
|
|
169
|
+
"--color-surface-elevated-secondary",
|
|
168
170
|
"--color-background-elevated-primary-opaque",
|
|
171
|
+
"--color-background-primary-ghost-hover",
|
|
172
|
+
"--color-chart-blue",
|
|
169
173
|
"--color-background-panel",
|
|
170
174
|
"--color-background-control",
|
|
171
175
|
"--color-background-primary-solid",
|
|
@@ -181,11 +185,13 @@ export function createInjectionSource(features, {
|
|
|
181
185
|
"--color-text-foreground",
|
|
182
186
|
"--color-text-foreground-secondary",
|
|
183
187
|
"--color-text-foreground-tertiary",
|
|
188
|
+
"--color-text-inverse",
|
|
184
189
|
"--color-text-on-accent",
|
|
185
190
|
"--color-text-button-primary",
|
|
186
191
|
"--color-text-accent",
|
|
187
192
|
"--color-token-text-link-foreground",
|
|
188
193
|
"--color-border",
|
|
194
|
+
"--color-border-subtle",
|
|
189
195
|
"--color-border-heavy",
|
|
190
196
|
"--color-border-focus",
|
|
191
197
|
"--color-token-scrollbar-slider-background",
|
|
@@ -306,6 +312,7 @@ export function createInjectionSource(features, {
|
|
|
306
312
|
let modelSelectorSuppressOutsidePointerSequence = false;
|
|
307
313
|
let modelSelectorTriggerObserver = null;
|
|
308
314
|
let modelSelectorObservedTrigger = null;
|
|
315
|
+
let modelSelectorControllerMenu = null;
|
|
309
316
|
let modelSelectorNativeFastIcons = null;
|
|
310
317
|
// Fast 图标来源标记:bundle 静态提取 vs DOM 克隆,DOM 克隆优先以保持像素一致。
|
|
311
318
|
let modelSelectorNativeFastIconSources = null;
|
|
@@ -318,6 +325,15 @@ export function createInjectionSource(features, {
|
|
|
318
325
|
modelSelectorStyle.setAttribute("data-codex-model-slider-style", "");
|
|
319
326
|
modelSelectorStyle.textContent = `
|
|
320
327
|
[data-codex-model-slider-menu] { width: 264px !important; min-width: 264px; overflow-x: hidden; }
|
|
328
|
+
[data-codex-model-slider-controller-menu] {
|
|
329
|
+
position: absolute !important;
|
|
330
|
+
right: 0;
|
|
331
|
+
bottom: 0;
|
|
332
|
+
z-index: 1;
|
|
333
|
+
background-color: var(--color-surface-elevated-secondary);
|
|
334
|
+
-webkit-backdrop-filter: none;
|
|
335
|
+
backdrop-filter: none;
|
|
336
|
+
}
|
|
321
337
|
[data-codex-model-slider-menu][data-codex-model-slider-overflow] {
|
|
322
338
|
max-height: var(--codex-model-slider-menu-max-height) !important;
|
|
323
339
|
overflow-y: auto;
|
|
@@ -935,7 +951,7 @@ export function createInjectionSource(features, {
|
|
|
935
951
|
function modelDisplayLabel(identity, selector) {
|
|
936
952
|
const model = catalogModelFor(identity, selector) ?? nativeCatalogModelFor(identity);
|
|
937
953
|
const source = model?.displayName || model?.slug || identity.backend;
|
|
938
|
-
return formatIdentifier(
|
|
954
|
+
return formatIdentifier(source.slice(source.lastIndexOf("/") + 1));
|
|
939
955
|
}
|
|
940
956
|
|
|
941
957
|
function brandForModel(identity, selector) {
|
|
@@ -952,10 +968,21 @@ export function createInjectionSource(features, {
|
|
|
952
968
|
.trim();
|
|
953
969
|
}
|
|
954
970
|
|
|
971
|
+
function currentReactFiber(element) {
|
|
972
|
+
const fiberKey = Object.getOwnPropertyNames(element ?? {}).find((key) => key.startsWith("__reactFiber$"));
|
|
973
|
+
const propsKey = Object.getOwnPropertyNames(element ?? {}).find((key) => key.startsWith("__reactProps$"));
|
|
974
|
+
let fiber = fiberKey ? element[fiberKey] : null;
|
|
975
|
+
if (
|
|
976
|
+
propsKey
|
|
977
|
+
&& fiber?.alternate?.memoizedProps === element[propsKey]
|
|
978
|
+
&& fiber.memoizedProps !== element[propsKey]
|
|
979
|
+
) return fiber.alternate;
|
|
980
|
+
return fiber;
|
|
981
|
+
}
|
|
982
|
+
|
|
955
983
|
function nativeComposerModelController() {
|
|
956
984
|
const trigger = document.querySelector("[data-codex-intelligence-trigger]");
|
|
957
|
-
|
|
958
|
-
let fiber = fiberKey ? trigger[fiberKey] : null;
|
|
985
|
+
let fiber = currentReactFiber(trigger);
|
|
959
986
|
for (let depth = 0; fiber && depth < 40; depth += 1, fiber = fiber.return) {
|
|
960
987
|
const props = fiber.memoizedProps;
|
|
961
988
|
if (Array.isArray(props?.models) && typeof props.onSelectReasoningEffort === "function") return props;
|
|
@@ -1480,8 +1507,12 @@ export function createInjectionSource(features, {
|
|
|
1480
1507
|
renderEnhancedModelMenu(menu, selector);
|
|
1481
1508
|
}
|
|
1482
1509
|
|
|
1483
|
-
function enhanceModelItem(item, menu, selector, index
|
|
1484
|
-
|
|
1510
|
+
function enhanceModelItem(item, menu, selector, index, {
|
|
1511
|
+
rawValue = null,
|
|
1512
|
+
selected = null,
|
|
1513
|
+
scheduleReturn = true,
|
|
1514
|
+
} = {}) {
|
|
1515
|
+
const observedRaw = rawValue ?? modelItemRawValue(item);
|
|
1485
1516
|
if (!observedRaw) return;
|
|
1486
1517
|
const observedIdentity = modelIdentity(observedRaw);
|
|
1487
1518
|
const nativeDescriptor = nativeModelDescriptorForItem(item, observedRaw);
|
|
@@ -1499,7 +1530,10 @@ export function createInjectionSource(features, {
|
|
|
1499
1530
|
const display = modelDisplayLabel(identity, selector);
|
|
1500
1531
|
const provider = providerLabel(identity.provider, selector);
|
|
1501
1532
|
const brand = brandForModel(identity, selector);
|
|
1502
|
-
item.toggleAttribute(
|
|
1533
|
+
item.toggleAttribute(
|
|
1534
|
+
"data-codex-model-slider-selected",
|
|
1535
|
+
selected ?? Boolean(item.querySelector("svg")),
|
|
1536
|
+
);
|
|
1503
1537
|
const favorites = loadModelFavorites(selector);
|
|
1504
1538
|
const starred = favorites.has(raw);
|
|
1505
1539
|
const row = document.createElement("span");
|
|
@@ -1528,7 +1562,7 @@ export function createInjectionSource(features, {
|
|
|
1528
1562
|
star.append(createModelSelectorIcon(starred ? selector.icons.unstar : selector.icons.star));
|
|
1529
1563
|
row.append(star);
|
|
1530
1564
|
item.replaceChildren(row);
|
|
1531
|
-
if (!item.hasAttribute("data-codex-model-slider-return-listener")) {
|
|
1565
|
+
if (scheduleReturn && !item.hasAttribute("data-codex-model-slider-return-listener")) {
|
|
1532
1566
|
item.setAttribute("data-codex-model-slider-return-listener", "");
|
|
1533
1567
|
item.addEventListener("click", (event) => {
|
|
1534
1568
|
if (!event.target.closest?.("[data-codex-model-slider-star]")) scheduleModelSliderReturn();
|
|
@@ -1665,6 +1699,106 @@ export function createInjectionSource(features, {
|
|
|
1665
1699
|
}
|
|
1666
1700
|
}
|
|
1667
1701
|
|
|
1702
|
+
function closeControllerModelMenu() {
|
|
1703
|
+
const record = modelSelectorControllerMenu;
|
|
1704
|
+
if (!record) return false;
|
|
1705
|
+
record.button.setAttribute("aria-expanded", "false");
|
|
1706
|
+
record.parentMenu.style.position = record.position;
|
|
1707
|
+
record.parentMenu.style.overflow = record.overflow;
|
|
1708
|
+
for (const item of modelMenuItems(record.menu)) modelSelectorOriginalItems.delete(item);
|
|
1709
|
+
record.menu.remove();
|
|
1710
|
+
modelSelectorControllerMenu = null;
|
|
1711
|
+
return true;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
function controllerModelReasoningEffort(controller, model) {
|
|
1715
|
+
const current = String(controller?.reasoningEffort ?? "").toLowerCase();
|
|
1716
|
+
const supported = (model?.supportedReasoningEfforts ?? []).map((entry) => (
|
|
1717
|
+
String(entry?.reasoningEffort ?? entry ?? "").toLowerCase()
|
|
1718
|
+
)).filter(Boolean);
|
|
1719
|
+
return supported.includes(current)
|
|
1720
|
+
? current
|
|
1721
|
+
: String(model?.defaultReasoningEffort ?? supported[0] ?? "none").toLowerCase();
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
function openControllerModelMenu(parentMenu, modelButton, selector) {
|
|
1725
|
+
if (modelSelectorControllerMenu) {
|
|
1726
|
+
closeControllerModelMenu();
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
const controller = nativeComposerModelController();
|
|
1730
|
+
if (!Array.isArray(controller?.models) || typeof controller.onSelectModel !== "function") return;
|
|
1731
|
+
const models = controller.models.filter((model) => !model.hidden && String(model.model ?? "").trim());
|
|
1732
|
+
if (!models.length) return;
|
|
1733
|
+
|
|
1734
|
+
const menu = document.createElement("div");
|
|
1735
|
+
menu.className = parentMenu.className;
|
|
1736
|
+
menu.setAttribute("role", "menu");
|
|
1737
|
+
menu.setAttribute("data-state", "open");
|
|
1738
|
+
menu.setAttribute("data-codex-model-slider-menu", "");
|
|
1739
|
+
menu.setAttribute("data-codex-model-slider-controller-menu", "");
|
|
1740
|
+
models.forEach((model, index) => {
|
|
1741
|
+
const item = document.createElement("div");
|
|
1742
|
+
item.className = "no-drag outline-hidden rounded-lg px-[var(--padding-row-x)] py-[var(--padding-row-y)] text-sm text-default group hover:bg-primary-ghost-hover focus:bg-primary-ghost-hover cursor-interaction flex flex-col";
|
|
1743
|
+
item.setAttribute("role", "menuitem");
|
|
1744
|
+
item.setAttribute("tabindex", "-1");
|
|
1745
|
+
enhanceModelItem(item, menu, selector, index, {
|
|
1746
|
+
rawValue: String(model.model),
|
|
1747
|
+
selected: String(model.model) === String(controller.model),
|
|
1748
|
+
scheduleReturn: false,
|
|
1749
|
+
});
|
|
1750
|
+
menu.append(item);
|
|
1751
|
+
});
|
|
1752
|
+
menu.addEventListener("click", (event) => {
|
|
1753
|
+
const star = event.target.closest?.("[data-codex-model-slider-star]");
|
|
1754
|
+
const item = event.target.closest?.("[data-codex-model-slider-item]");
|
|
1755
|
+
if (!item) return;
|
|
1756
|
+
if (star) {
|
|
1757
|
+
const raw = item.dataset.codexModelSliderRaw;
|
|
1758
|
+
const label = item.querySelector("[data-codex-model-slider-name]")?.textContent ?? raw;
|
|
1759
|
+
toggleModelFavorite(event, menu, selector, raw, label);
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
const currentController = nativeComposerModelController();
|
|
1763
|
+
const raw = item.dataset.codexModelSliderRaw;
|
|
1764
|
+
const model = currentController?.models?.find((candidate) => String(candidate.model) === raw);
|
|
1765
|
+
if (!model || typeof currentController.onSelectModel !== "function") return;
|
|
1766
|
+
const reasoningEffort = controllerModelReasoningEffort(currentController, model);
|
|
1767
|
+
currentController.onSelectModel(raw, reasoningEffort);
|
|
1768
|
+
closeControllerModelMenu();
|
|
1769
|
+
queueEnsure();
|
|
1770
|
+
});
|
|
1771
|
+
menu.addEventListener("keydown", (event) => {
|
|
1772
|
+
if (event.key === "Escape") {
|
|
1773
|
+
event.preventDefault();
|
|
1774
|
+
closeControllerModelMenu();
|
|
1775
|
+
modelButton.focus();
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1779
|
+
const item = event.target.closest?.("[data-codex-model-slider-item]");
|
|
1780
|
+
if (!item) return;
|
|
1781
|
+
event.preventDefault();
|
|
1782
|
+
item.click();
|
|
1783
|
+
});
|
|
1784
|
+
renderEnhancedModelMenu(menu, selector);
|
|
1785
|
+
|
|
1786
|
+
modelSelectorControllerMenu = {
|
|
1787
|
+
menu,
|
|
1788
|
+
parentMenu,
|
|
1789
|
+
button: modelButton,
|
|
1790
|
+
position: parentMenu.style.position,
|
|
1791
|
+
overflow: parentMenu.style.overflow,
|
|
1792
|
+
};
|
|
1793
|
+
parentMenu.style.position = "relative";
|
|
1794
|
+
parentMenu.style.overflow = "visible";
|
|
1795
|
+
parentMenu.append(menu);
|
|
1796
|
+
sizeModelMenuViewport(menu, selector.maxVisibleItems);
|
|
1797
|
+
modelButton.setAttribute("aria-expanded", "true");
|
|
1798
|
+
(menu.querySelector('[data-codex-model-slider-selected]')
|
|
1799
|
+
?? menu.querySelector('[role="menuitem"]'))?.focus();
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1668
1802
|
function openNativeModelSubmenu(parentMenu) {
|
|
1669
1803
|
let attempts = 0;
|
|
1670
1804
|
const open = () => {
|
|
@@ -1942,6 +2076,8 @@ export function createInjectionSource(features, {
|
|
|
1942
2076
|
modelButton.setAttribute("tabindex", "0");
|
|
1943
2077
|
modelButton.setAttribute("data-codex-model-slider-model-button", "");
|
|
1944
2078
|
modelButton.setAttribute("aria-label", `Model ${modelDisplayLabel(identity, selector)}`);
|
|
2079
|
+
modelButton.setAttribute("aria-haspopup", "menu");
|
|
2080
|
+
modelButton.setAttribute("aria-expanded", "false");
|
|
1945
2081
|
const buttonContent = document.createElement("span");
|
|
1946
2082
|
buttonContent.className = classes.ViewToggleContent;
|
|
1947
2083
|
const buttonLabel = document.createElement("span");
|
|
@@ -1955,7 +2091,7 @@ export function createInjectionSource(features, {
|
|
|
1955
2091
|
buttonContent.append(buttonLabel);
|
|
1956
2092
|
}
|
|
1957
2093
|
modelButton.append(buttonContent);
|
|
1958
|
-
const openModels = () =>
|
|
2094
|
+
const openModels = () => openControllerModelMenu(parentMenu, modelButton, selector);
|
|
1959
2095
|
modelButton.addEventListener("click", openModels);
|
|
1960
2096
|
modelButton.addEventListener("keydown", (event) => {
|
|
1961
2097
|
if (event.key !== "Enter" && event.key !== " ") return;
|
|
@@ -2006,6 +2142,11 @@ export function createInjectionSource(features, {
|
|
|
2006
2142
|
viewControls.append(modelButton);
|
|
2007
2143
|
}
|
|
2008
2144
|
|
|
2145
|
+
if (efforts.length < 2) {
|
|
2146
|
+
shell.append(viewControls);
|
|
2147
|
+
return shell;
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2009
2150
|
const nativeSlider = cloneNativePowerSlider(parentMenu, efforts.length, fastEnabled);
|
|
2010
2151
|
const rail = nativeSlider?.querySelector('[role="slider"]') ?? document.createElement("div");
|
|
2011
2152
|
rail.setAttribute("data-codex-model-slider-rail", "");
|
|
@@ -2126,7 +2267,7 @@ export function createInjectionSource(features, {
|
|
|
2126
2267
|
supportedReasoningLevels: model?.supportedReasoningLevels ?? catalogModel?.supportedReasoningLevels ?? [],
|
|
2127
2268
|
};
|
|
2128
2269
|
const efforts = visibleReasoningLevelsFor(modelWithCapability);
|
|
2129
|
-
if (!model
|
|
2270
|
+
if (!model) return false;
|
|
2130
2271
|
// 控制器推导的任务内实际 effort 优先,避免子任务/主任务切换后触发器属性滞后;
|
|
2131
2272
|
// 触发器属性仅在控制器无法解析时作为实时回退,静态目录默认值最后兜底。
|
|
2132
2273
|
const nativeModel = nativeCatalogModelFor(identity);
|
|
@@ -2134,7 +2275,8 @@ export function createInjectionSource(features, {
|
|
|
2134
2275
|
?? document.querySelector("[data-codex-intelligence-trigger]")
|
|
2135
2276
|
?.getAttribute("data-selected-reasoning-effort")
|
|
2136
2277
|
?? model.defaultReasoningLevel
|
|
2137
|
-
?? efforts[0]
|
|
2278
|
+
?? efforts[0]?.effort
|
|
2279
|
+
?? "none";
|
|
2138
2280
|
const effortRank = (value) => {
|
|
2139
2281
|
const order = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
|
|
2140
2282
|
const index = order.indexOf(String(value ?? "").toLowerCase());
|
|
@@ -2143,7 +2285,8 @@ export function createInjectionSource(features, {
|
|
|
2143
2285
|
const visibleEffort = efforts.find((level) => level.effort === selectedEffort)?.effort
|
|
2144
2286
|
?? efforts.filter((level) => effortRank(level.effort) <= effortRank(selectedEffort))
|
|
2145
2287
|
.sort((left, right) => effortRank(right.effort) - effortRank(left.effort))[0]?.effort
|
|
2146
|
-
?? efforts[0]
|
|
2288
|
+
?? efforts[0]?.effort
|
|
2289
|
+
?? "none";
|
|
2147
2290
|
const selectedEffortValue = visibleEffort;
|
|
2148
2291
|
const selectedServiceTier = nativeComposerModelController()?.selectedServiceTier;
|
|
2149
2292
|
const serviceTierKey = selectedServiceTier?.id ?? selectedServiceTier ?? "standard";
|
|
@@ -2252,12 +2395,18 @@ export function createInjectionSource(features, {
|
|
|
2252
2395
|
if (!modelSelectorStyle.isConnected) document.head?.append(modelSelectorStyle);
|
|
2253
2396
|
enhanceComposerModelTrigger(selector);
|
|
2254
2397
|
const parentMenu = nativeModelPickerMenu();
|
|
2398
|
+
if (modelSelectorControllerMenu && modelSelectorControllerMenu.parentMenu !== parentMenu) {
|
|
2399
|
+
closeControllerModelMenu();
|
|
2400
|
+
}
|
|
2255
2401
|
if (!parentMenu) {
|
|
2256
|
-
if (
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2402
|
+
if (
|
|
2403
|
+
modelSelectorPendingReturn
|
|
2404
|
+
&& !visibleBlockingDialog()
|
|
2405
|
+
&& nativeModelPickerSubmenus().length === 0
|
|
2406
|
+
) {
|
|
2407
|
+
clearTimeout(modelSelectorReturnTimer);
|
|
2408
|
+
modelSelectorReturnTimer = null;
|
|
2409
|
+
returnToNativeModelSlider();
|
|
2261
2410
|
}
|
|
2262
2411
|
} else {
|
|
2263
2412
|
enhanceNativeModelPicker(parentMenu, selector);
|
|
@@ -2273,6 +2422,7 @@ export function createInjectionSource(features, {
|
|
|
2273
2422
|
}
|
|
2274
2423
|
|
|
2275
2424
|
function restoreModelSelectorEnhancements() {
|
|
2425
|
+
closeControllerModelMenu();
|
|
2276
2426
|
clearTimeout(modelSelectorReturnTimer);
|
|
2277
2427
|
clearTimeout(modelSelectorEffortTransactionTimer);
|
|
2278
2428
|
modelSelectorTriggerObserver?.disconnect();
|
|
@@ -2432,6 +2582,8 @@ export function createInjectionSource(features, {
|
|
|
2432
2582
|
const dialogShadow = "0 16px 32px -8px rgba(0,0,0,.19)";
|
|
2433
2583
|
const surfaceTokens = {
|
|
2434
2584
|
...tokens,
|
|
2585
|
+
// 部分 Codex 版本仅提供等价的正文前景 token。
|
|
2586
|
+
"--color-token-input-foreground": tokens["--color-token-input-foreground"] ?? tokens["--color-text-foreground"],
|
|
2435
2587
|
"--codex-summary-section-title-font-size": "14px",
|
|
2436
2588
|
"--codex-summary-section-title-line-height": "21px",
|
|
2437
2589
|
"--codex-summary-section-title-font-weight": tokens["--font-weight-normal"] ?? "400",
|
|
@@ -2687,8 +2839,9 @@ export function createInjectionSource(features, {
|
|
|
2687
2839
|
postSurfaceActive(record, false);
|
|
2688
2840
|
}
|
|
2689
2841
|
const existing = pageSurfaces.get(featureId);
|
|
2690
|
-
const
|
|
2691
|
-
|
|
2842
|
+
const reusable = existing?.isConnected ? existing : null;
|
|
2843
|
+
const surface = reusable ?? createPageSurface(feature);
|
|
2844
|
+
if (reusable) reloadSurfaceIfUnready(surfaceKey(feature.id, "page"), feature.surfaceUrl);
|
|
2692
2845
|
surface.hidden = false;
|
|
2693
2846
|
activePageFeatureId = featureId;
|
|
2694
2847
|
const record = surfaceRecords.get(surfaceKey(feature.id, "page"));
|
|
@@ -3821,7 +3974,7 @@ export function createInjectionSource(features, {
|
|
|
3821
3974
|
respondToSurface(record, requestId, { ok: true, result: { paths } });
|
|
3822
3975
|
return;
|
|
3823
3976
|
}
|
|
3824
|
-
if (
|
|
3977
|
+
if (["attach-file", "create-image-edit-thread"].includes(action)) {
|
|
3825
3978
|
if (typeof globalThis[config.bindingName] !== "function") {
|
|
3826
3979
|
throw new Error("The native file bridge is unavailable");
|
|
3827
3980
|
}
|
|
@@ -3863,6 +4016,11 @@ export function createInjectionSource(features, {
|
|
|
3863
4016
|
document.addEventListener(
|
|
3864
4017
|
"click",
|
|
3865
4018
|
(event) => {
|
|
4019
|
+
if (
|
|
4020
|
+
modelSelectorControllerMenu
|
|
4021
|
+
&& !modelSelectorControllerMenu.menu.contains(event.target)
|
|
4022
|
+
&& !modelSelectorControllerMenu.button.contains(event.target)
|
|
4023
|
+
) closeControllerModelMenu();
|
|
3866
4024
|
if (isNativeSummaryButton(event.target) && activePinnedFeatureId) hidePinnedSurfaces();
|
|
3867
4025
|
if (activePinnedFeatureId && summaryDisplayMode() === "overlay") {
|
|
3868
4026
|
const state = summaryState(activePinnedFeatureId);
|
|
@@ -4010,6 +4168,7 @@ export function createInjectionSource(features, {
|
|
|
4010
4168
|
stopToolbarReadiness();
|
|
4011
4169
|
mainContentObserver?.observer.disconnect();
|
|
4012
4170
|
restoreThreadContentShift();
|
|
4171
|
+
for (const entry of document.querySelectorAll(`[${entryMarker}]`)) entry.remove();
|
|
4013
4172
|
for (const root of document.querySelectorAll("[data-codex-personal-toolbar-entry]")) root.remove();
|
|
4014
4173
|
for (const group of document.querySelectorAll("[data-codex-personal-summary-toolbar-group]")) group.remove();
|
|
4015
4174
|
for (const surface of pageSurfaces.values()) surface.remove();
|
|
@@ -4659,6 +4818,143 @@ export class CodexRuntime {
|
|
|
4659
4818
|
}
|
|
4660
4819
|
}
|
|
4661
4820
|
|
|
4821
|
+
async attachFilesToComposer(client, filePaths, requestId) {
|
|
4822
|
+
if (!Array.isArray(filePaths) || !filePaths.length || filePaths.length > 2) {
|
|
4823
|
+
throw new Error("One or two image edit attachments are required");
|
|
4824
|
+
}
|
|
4825
|
+
for (const filePath of filePaths) {
|
|
4826
|
+
if (typeof filePath !== "string" || !path.isAbsolute(filePath) || !(await stat(filePath)).isFile()) {
|
|
4827
|
+
throw new Error("Only absolute regular files can be attached");
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
const fileNames = filePaths.map((filePath) => path.basename(filePath));
|
|
4831
|
+
const requiredLabels = fileNames.reduce((counts, fileName) => {
|
|
4832
|
+
const label = `Remove ${fileName}`;
|
|
4833
|
+
counts[label] = (counts[label] ?? 0) + 1;
|
|
4834
|
+
return counts;
|
|
4835
|
+
}, {});
|
|
4836
|
+
const marker = `codex-personal-${requestId}-batch`;
|
|
4837
|
+
|
|
4838
|
+
try {
|
|
4839
|
+
evaluationValue(await client.send("Runtime.evaluate", {
|
|
4840
|
+
expression: `(() => {
|
|
4841
|
+
document.querySelectorAll('[data-codex-personal-file-input]').forEach((input) => input.remove());
|
|
4842
|
+
const input = document.createElement("input");
|
|
4843
|
+
input.type = "file";
|
|
4844
|
+
input.multiple = true;
|
|
4845
|
+
input.dataset.codexPersonalFileInput = ${JSON.stringify(marker)};
|
|
4846
|
+
input.style.display = "none";
|
|
4847
|
+
document.body.append(input);
|
|
4848
|
+
return true;
|
|
4849
|
+
})()`,
|
|
4850
|
+
returnByValue: true,
|
|
4851
|
+
}));
|
|
4852
|
+
const { root } = await client.send("DOM.getDocument", { depth: 0 });
|
|
4853
|
+
const { nodeId } = await client.send("DOM.querySelector", {
|
|
4854
|
+
nodeId: root.nodeId,
|
|
4855
|
+
selector: `[data-codex-personal-file-input="${marker}"]`,
|
|
4856
|
+
});
|
|
4857
|
+
if (!nodeId) throw new Error("The temporary multi-file bridge could not be resolved");
|
|
4858
|
+
await client.send("DOM.setFileInputFiles", { files: filePaths, nodeId });
|
|
4859
|
+
const dropped = evaluationValue(await client.send("Runtime.evaluate", {
|
|
4860
|
+
expression: `(() => {
|
|
4861
|
+
const input = document.querySelector(${JSON.stringify(`[data-codex-personal-file-input="${marker}"]`)});
|
|
4862
|
+
const composer = document.querySelector('[data-codex-composer="true"][contenteditable="true"]');
|
|
4863
|
+
if (!input?.files?.length || !composer) return false;
|
|
4864
|
+
const transfer = new DataTransfer();
|
|
4865
|
+
for (const file of input.files) transfer.items.add(file);
|
|
4866
|
+
for (const type of ["dragenter", "dragover", "drop"]) {
|
|
4867
|
+
composer.dispatchEvent(new DragEvent(type, {
|
|
4868
|
+
bubbles: true,
|
|
4869
|
+
cancelable: true,
|
|
4870
|
+
composed: true,
|
|
4871
|
+
dataTransfer: transfer,
|
|
4872
|
+
}));
|
|
4873
|
+
}
|
|
4874
|
+
return transfer.files.length === ${filePaths.length};
|
|
4875
|
+
})()`,
|
|
4876
|
+
returnByValue: true,
|
|
4877
|
+
}));
|
|
4878
|
+
if (!dropped) throw new Error("The native Composer rejected the image edit drop");
|
|
4879
|
+
await waitForExpression(client, `(() => {
|
|
4880
|
+
const required = ${JSON.stringify(requiredLabels)};
|
|
4881
|
+
const observed = {};
|
|
4882
|
+
for (const button of document.querySelectorAll('button[aria-label^="Remove "]')) {
|
|
4883
|
+
const label = button.getAttribute("aria-label");
|
|
4884
|
+
observed[label] = (observed[label] ?? 0) + 1;
|
|
4885
|
+
}
|
|
4886
|
+
return Object.entries(required).every(([label, count]) => (observed[label] ?? 0) >= count);
|
|
4887
|
+
})()`, 10_000);
|
|
4888
|
+
return fileNames.map((name) => ({ mode: "attach", name }));
|
|
4889
|
+
} finally {
|
|
4890
|
+
await client.send("Runtime.evaluate", {
|
|
4891
|
+
expression: `document.querySelectorAll('[data-codex-personal-file-input]').forEach((input) => input.remove())`,
|
|
4892
|
+
}).catch(() => {});
|
|
4893
|
+
}
|
|
4894
|
+
}
|
|
4895
|
+
|
|
4896
|
+
async createImageEditThread(client, feature, payload, requestId) {
|
|
4897
|
+
const draftId = typeof payload?.draftId === "string" ? payload.draftId : "";
|
|
4898
|
+
const prompt = typeof payload?.prompt === "string" ? payload.prompt.trim() : "";
|
|
4899
|
+
if (!/^[a-f0-9-]{36}$/.test(draftId) || !prompt || prompt.length > 2_000 || !feature?.surfaceUrl) {
|
|
4900
|
+
throw Object.assign(new Error("The image edit request is incomplete"), { code: "INVALID_EDIT_REQUEST" });
|
|
4901
|
+
}
|
|
4902
|
+
const draftResponse = await this.fetchImpl(new URL(`/api/image-edit-draft/${draftId}`, feature.surfaceUrl), {
|
|
4903
|
+
headers: { Origin: CODEX_APP_ORIGIN },
|
|
4904
|
+
});
|
|
4905
|
+
if (!draftResponse?.ok) {
|
|
4906
|
+
throw Object.assign(new Error("The image edit draft is unavailable"), { code: "EDIT_DRAFT_UNAVAILABLE" });
|
|
4907
|
+
}
|
|
4908
|
+
const draft = await draftResponse.json();
|
|
4909
|
+
const paths = [draft?.sourcePath, draft?.auxiliaryPath].filter(Boolean);
|
|
4910
|
+
if (!paths.length || paths.length > 2) {
|
|
4911
|
+
throw Object.assign(new Error("The image edit attachments are unavailable"), { code: "INVALID_EDIT_ATTACHMENT" });
|
|
4912
|
+
}
|
|
4913
|
+
for (const filePath of paths) {
|
|
4914
|
+
if (typeof filePath !== "string" || !path.isAbsolute(filePath) || !(await stat(filePath)).isFile()) {
|
|
4915
|
+
throw Object.assign(new Error("The image edit attachment is unavailable"), { code: "INVALID_EDIT_ATTACHMENT" });
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
const opened = evaluationValue(await client.send("Runtime.evaluate", {
|
|
4920
|
+
expression: `(() => {
|
|
4921
|
+
const candidates = Array.from(document.querySelectorAll('button, [role="button"]'));
|
|
4922
|
+
const trigger = candidates.find((element) => /^new chat$/i.test(
|
|
4923
|
+
(element.getAttribute('aria-label') || element.textContent || '').trim(),
|
|
4924
|
+
));
|
|
4925
|
+
if (!trigger) return false;
|
|
4926
|
+
trigger.click();
|
|
4927
|
+
return true;
|
|
4928
|
+
})()`,
|
|
4929
|
+
returnByValue: true,
|
|
4930
|
+
}));
|
|
4931
|
+
if (!opened) throw Object.assign(new Error("The native New chat control is unavailable"), {
|
|
4932
|
+
code: "NEW_CHAT_UNAVAILABLE",
|
|
4933
|
+
});
|
|
4934
|
+
await waitForExpression(client, `Boolean(document.querySelector('[data-testid="home-icon"]')) && Boolean(document.querySelector('[data-codex-composer="true"][contenteditable="true"]'))`);
|
|
4935
|
+
const attached = await this.attachFilesToComposer(client, paths, requestId);
|
|
4936
|
+
evaluationValue(await client.send("Runtime.evaluate", {
|
|
4937
|
+
expression: `window.__codexPersonalRuntime?.insertText(${JSON.stringify(prompt)})`,
|
|
4938
|
+
returnByValue: true,
|
|
4939
|
+
}));
|
|
4940
|
+
const sent = evaluationValue(await client.send("Runtime.evaluate", {
|
|
4941
|
+
expression: `(() => {
|
|
4942
|
+
const send = document.querySelector('button[aria-label="Send message"]')
|
|
4943
|
+
|| document.querySelector('button[aria-label="Send"]')
|
|
4944
|
+
|| document.querySelector('button[data-testid="send-button"]');
|
|
4945
|
+
if (!send || send.disabled) return false;
|
|
4946
|
+
send.click();
|
|
4947
|
+
return true;
|
|
4948
|
+
})()`,
|
|
4949
|
+
returnByValue: true,
|
|
4950
|
+
}));
|
|
4951
|
+
if (!sent) throw Object.assign(new Error("The new Chat is ready but its Send button is unavailable"), {
|
|
4952
|
+
code: "SEND_UNAVAILABLE",
|
|
4953
|
+
});
|
|
4954
|
+
await waitForExpression(client, `!document.querySelector('[data-testid="home-icon"]') && Boolean(document.querySelector('[data-codex-composer="true"][contenteditable="true"]'))`);
|
|
4955
|
+
return { attached, threadStarted: true };
|
|
4956
|
+
}
|
|
4957
|
+
|
|
4662
4958
|
async fetchSurfaceResource(url, label) {
|
|
4663
4959
|
const controller = new AbortController();
|
|
4664
4960
|
const timeout = setTimeout(() => controller.abort(), this.surfaceLoadTimeoutMs);
|
|
@@ -4793,8 +5089,9 @@ export class CodexRuntime {
|
|
|
4793
5089
|
return;
|
|
4794
5090
|
}
|
|
4795
5091
|
try {
|
|
4796
|
-
|
|
4797
|
-
|
|
5092
|
+
const result = request.action === "create-image-edit-thread"
|
|
5093
|
+
? await this.createImageEditThread(client, feature, request.payload, request.requestId)
|
|
5094
|
+
: await this.attachFileToComposer(client, request.payload?.path, request.requestId);
|
|
4798
5095
|
await this.resolveHostAction(client, request.requestId, { ok: true, result });
|
|
4799
5096
|
} catch (error) {
|
|
4800
5097
|
await this.resolveHostAction(client, request.requestId, {
|
|
@@ -12,6 +12,7 @@ const HOST_ACTIONS = new Set([
|
|
|
12
12
|
"open-editor-modal",
|
|
13
13
|
"resolve-file-paths",
|
|
14
14
|
"import-generated-image",
|
|
15
|
+
"create-image-edit-thread",
|
|
15
16
|
]);
|
|
16
17
|
|
|
17
18
|
const PAGE_SCRIPT_ENTRY_KINDS = new Set(["page-script"]);
|
|
@@ -359,6 +360,12 @@ export async function loadConfiguredModelCatalog(
|
|
|
359
360
|
id: String(tier.id ?? "").trim(),
|
|
360
361
|
name: String(tier.name ?? "").trim(),
|
|
361
362
|
})),
|
|
363
|
+
capabilityKey: [
|
|
364
|
+
model.opencodex_capability_provenance?.provider,
|
|
365
|
+
model.opencodex_capability_provenance?.model_id,
|
|
366
|
+
].every((value) => typeof value === "string" && value.trim())
|
|
367
|
+
? `${model.opencodex_capability_provenance.provider.trim()}/${model.opencodex_capability_provenance.model_id.trim()}`
|
|
368
|
+
: null,
|
|
362
369
|
})).filter((model) => model.slug);
|
|
363
370
|
} catch (error) {
|
|
364
371
|
if (error.code === "ENOENT") return [];
|
|
@@ -29,7 +29,7 @@ for (const feature of features) {
|
|
|
29
29
|
feature.modelSelector.models = modelCatalog.map((model) => (
|
|
30
30
|
applyVisibleReasoningLevels(
|
|
31
31
|
model,
|
|
32
|
-
modelCapabilities.get(String(model.slug ?? "").toLowerCase()),
|
|
32
|
+
modelCapabilities.get(String(model.capabilityKey ?? model.slug ?? "").toLowerCase()),
|
|
33
33
|
model.supportedReasoningLevels,
|
|
34
34
|
)
|
|
35
35
|
));
|