@steipete/oracle 0.14.0 → 0.14.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 -2
- package/dist/bin/oracle-cli.js +8 -8
- package/dist/src/browser/actions/assistantResponse.js +2 -1
- package/dist/src/browser/actions/modelSelection.js +344 -29
- package/dist/src/browser/actions/thinkingTime.js +239 -46
- package/dist/src/browser/chatgptImages.js +126 -24
- package/dist/src/browser/index.js +63 -8
- package/dist/src/browser/projectSourcesRunner.js +2 -1
- package/dist/src/browser/prompt.js +151 -22
- package/dist/src/cli/browserConfig.js +2 -1
- package/dist/src/cli/browserDefaults.js +2 -1
- package/dist/src/cli/options.js +8 -0
- package/dist/src/mcp/tools/chatgptImage.js +8 -3
- package/dist/src/mcp/tools/consult.js +9 -8
- package/dist/src/mcp/types.js +11 -2
- package/dist/src/oracle/thinkingTime.js +40 -0
- package/dist/vendor/oracle-notifier/build-notifier.sh +0 -0
- package/package.json +32 -34
- package/vendor/oracle-notifier/build-notifier.sh +0 -0
- package/dist/bin/oracle.js +0 -569
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +0 -1
- package/dist/docs-site/RELEASING.html +0 -410
- package/dist/docs-site/agents.html +0 -374
- package/dist/docs-site/anthropic.html +0 -368
- package/dist/docs-site/bridge.html +0 -400
- package/dist/docs-site/browser-mode.html +0 -593
- package/dist/docs-site/chromium-forks.html +0 -347
- package/dist/docs-site/cli-reference.html +0 -346
- package/dist/docs-site/configuration.html +0 -452
- package/dist/docs-site/favicon.svg +0 -14
- package/dist/docs-site/followup.html +0 -375
- package/dist/docs-site/gemini.html +0 -383
- package/dist/docs-site/grok.html +0 -325
- package/dist/docs-site/index.html +0 -360
- package/dist/docs-site/install.html +0 -335
- package/dist/docs-site/linux.html +0 -321
- package/dist/docs-site/llms.txt +0 -43
- package/dist/docs-site/manual-tests.html +0 -596
- package/dist/docs-site/mcp.html +0 -391
- package/dist/docs-site/multimodel.html +0 -364
- package/dist/docs-site/mythical-pro-agents.html +0 -360
- package/dist/docs-site/notifier.html +0 -338
- package/dist/docs-site/openai-endpoints.html +0 -387
- package/dist/docs-site/openrouter.html +0 -344
- package/dist/docs-site/quickstart.html +0 -369
- package/dist/docs-site/refactor/ux.html +0 -532
- package/dist/docs-site/sessions.html +0 -388
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +0 -79
- package/dist/docs-site/spec.html +0 -363
- package/dist/docs-site/testing.html +0 -320
- package/dist/docs-site/tui-debug.html +0 -326
- package/dist/docs-site/windows-work.html +0 -323
- package/dist/docs-site/windows.html +0 -320
- package/dist/src/browser/chromeCookies.js +0 -312
- package/dist/src/browser/keytarShim.js +0 -56
- package/dist/src/browser/windowsCookies.js +0 -219
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +0 -128
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +0 -128
- package/vendor/oracle-notifier/README.md +0 -26
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { MENU_CONTAINER_SELECTOR, MENU_ITEM_SELECTOR, MODEL_BUTTON_SELECTOR, } from "../constants.js";
|
|
2
2
|
import { logDomFailure } from "../domDebug.js";
|
|
3
3
|
import { buildClickDispatcher } from "./domEvents.js";
|
|
4
|
+
const BROWSER_THINKING_LOG_PREFIX = "[browser] Thinking time:";
|
|
5
|
+
function formatBrowserThinkingLog(message) {
|
|
6
|
+
return `${BROWSER_THINKING_LOG_PREFIX} ${message.replace(/^Thinking time:\s*/, "")}`;
|
|
7
|
+
}
|
|
4
8
|
/**
|
|
5
9
|
* Surfaces the model-picker snapshot captured alongside a failed detection.
|
|
6
10
|
*
|
|
@@ -26,13 +30,14 @@ export async function ensureThinkingTime(Runtime, level, logger, desiredModel) {
|
|
|
26
30
|
const result = await evaluateThinkingTimeSelection(Runtime, level, desiredModel);
|
|
27
31
|
const capitalizedLevel = level.charAt(0).toUpperCase() + level.slice(1);
|
|
28
32
|
const targetModelKind = inferThinkingTargetModelKind(desiredModel);
|
|
29
|
-
const
|
|
33
|
+
const observedModelKind = result && "modelKind" in result ? result.modelKind : null;
|
|
34
|
+
const strictProEffort = (targetModelKind === "pro" || observedModelKind === "pro") && level === "extended";
|
|
30
35
|
switch (result?.status) {
|
|
31
36
|
case "already-selected":
|
|
32
|
-
logger(
|
|
37
|
+
logger(formatBrowserThinkingLog(`${result.label ?? capitalizedLevel} (already selected)`));
|
|
33
38
|
return;
|
|
34
39
|
case "switched":
|
|
35
|
-
logger(
|
|
40
|
+
logger(formatBrowserThinkingLog(result.label ?? capitalizedLevel));
|
|
36
41
|
return;
|
|
37
42
|
case "chip-not-found":
|
|
38
43
|
case "menu-not-found":
|
|
@@ -50,7 +55,7 @@ export async function ensureThinkingTime(Runtime, level, logger, desiredModel) {
|
|
|
50
55
|
if (strictProEffort) {
|
|
51
56
|
throw new Error(`${message}; refusing to submit without confirmed Pro Extended.`);
|
|
52
57
|
}
|
|
53
|
-
logger(`${message}; continuing with ChatGPT default.`);
|
|
58
|
+
logger(formatBrowserThinkingLog(`${message}; continuing with ChatGPT default.`));
|
|
54
59
|
return;
|
|
55
60
|
}
|
|
56
61
|
default: {
|
|
@@ -59,7 +64,7 @@ export async function ensureThinkingTime(Runtime, level, logger, desiredModel) {
|
|
|
59
64
|
if (strictProEffort) {
|
|
60
65
|
throw new Error(`Thinking time: unknown outcome selecting ${capitalizedLevel}; refusing to submit without confirmed Pro Extended.`);
|
|
61
66
|
}
|
|
62
|
-
logger(`
|
|
67
|
+
logger(formatBrowserThinkingLog(`unknown outcome selecting ${capitalizedLevel}; continuing with ChatGPT default.`));
|
|
63
68
|
return;
|
|
64
69
|
}
|
|
65
70
|
}
|
|
@@ -75,10 +80,10 @@ export async function ensureThinkingTimeIfAvailable(Runtime, level, logger, desi
|
|
|
75
80
|
const capitalizedLevel = level.charAt(0).toUpperCase() + level.slice(1);
|
|
76
81
|
switch (result?.status) {
|
|
77
82
|
case "already-selected":
|
|
78
|
-
logger(
|
|
83
|
+
logger(formatBrowserThinkingLog(`${result.label ?? capitalizedLevel} (already selected)`));
|
|
79
84
|
return true;
|
|
80
85
|
case "switched":
|
|
81
|
-
logger(
|
|
86
|
+
logger(formatBrowserThinkingLog(result.label ?? capitalizedLevel));
|
|
82
87
|
return true;
|
|
83
88
|
case "chip-not-found":
|
|
84
89
|
case "menu-not-found":
|
|
@@ -86,12 +91,12 @@ export async function ensureThinkingTimeIfAvailable(Runtime, level, logger, desi
|
|
|
86
91
|
case "selection-unverified":
|
|
87
92
|
case "model-kind-not-found":
|
|
88
93
|
if (logger.verbose) {
|
|
89
|
-
logger(
|
|
94
|
+
logger(formatBrowserThinkingLog(`${result.status.replaceAll("-", " ")}; continuing with default.`));
|
|
90
95
|
}
|
|
91
96
|
return false;
|
|
92
97
|
default:
|
|
93
98
|
if (logger.verbose) {
|
|
94
|
-
logger("
|
|
99
|
+
logger(formatBrowserThinkingLog("unknown outcome; continuing with default."));
|
|
95
100
|
}
|
|
96
101
|
return false;
|
|
97
102
|
}
|
|
@@ -99,7 +104,7 @@ export async function ensureThinkingTimeIfAvailable(Runtime, level, logger, desi
|
|
|
99
104
|
catch (error) {
|
|
100
105
|
const message = error instanceof Error ? error.message : String(error);
|
|
101
106
|
if (logger.verbose) {
|
|
102
|
-
logger(`
|
|
107
|
+
logger(formatBrowserThinkingLog(`selection failed (${message}); continuing with default.`));
|
|
103
108
|
await logDomFailure(Runtime, logger, "thinking-time");
|
|
104
109
|
}
|
|
105
110
|
return false;
|
|
@@ -130,10 +135,10 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
130
135
|
|
|
131
136
|
// Bilingual matchers: English level token + observed Chinese variants.
|
|
132
137
|
const LEVEL_TOKENS = {
|
|
133
|
-
light: ['light', '轻'],
|
|
134
|
-
standard: ['standard', '标准'],
|
|
135
|
-
extended: ['extended', '扩展', '深度', '加强'],
|
|
136
|
-
heavy: ['heavy', '重度', '加重', '高'],
|
|
138
|
+
light: ['light', 'instant', '轻'],
|
|
139
|
+
standard: ['standard', 'medium', '标准'],
|
|
140
|
+
extended: ['extended', 'high', '扩展', '深度', '加强'],
|
|
141
|
+
heavy: ['heavy', 'extra high', '重度', '加重', '高'],
|
|
137
142
|
};
|
|
138
143
|
const targetTokens = LEVEL_TOKENS[TARGET_LEVEL] || [TARGET_LEVEL];
|
|
139
144
|
|
|
@@ -152,11 +157,36 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
152
157
|
.replace(/[^a-z0-9\\u4e00-\\u9fa5]+/g, ' ')
|
|
153
158
|
.replace(/\\s+/g, ' ')
|
|
154
159
|
.trim();
|
|
160
|
+
const hasToken = (text, token) => normalize(text).split(' ').includes(token);
|
|
155
161
|
const matchesLevel = (text) => {
|
|
156
162
|
const t = normalize(text);
|
|
157
|
-
|
|
163
|
+
if (!t) return false;
|
|
164
|
+
return targetTokens.some((tok) => {
|
|
165
|
+
const token = normalize(tok);
|
|
166
|
+
if (!token) return false;
|
|
167
|
+
if (token === 'high') return hasToken(t, 'high') && !hasToken(t, 'extra');
|
|
168
|
+
if (token === 'extra high') return hasToken(t, 'extra') && hasToken(t, 'high');
|
|
169
|
+
return t === token || hasToken(t, token) || t.includes(token);
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
const matchesAnyEffortLevel = (text) => {
|
|
173
|
+
const normalizedText = normalize(text);
|
|
174
|
+
if (!normalizedText) return false;
|
|
175
|
+
for (const tokens of Object.values(LEVEL_TOKENS)) {
|
|
176
|
+
for (const rawToken of tokens) {
|
|
177
|
+
const token = normalize(rawToken);
|
|
178
|
+
if (!token) continue;
|
|
179
|
+
if (token.includes(' ')) {
|
|
180
|
+
if (token.split(' ').every((part) => hasToken(normalizedText, part))) return true;
|
|
181
|
+
} else if (/^[a-z0-9]+$/.test(token)) {
|
|
182
|
+
if (hasToken(normalizedText, token)) return true;
|
|
183
|
+
} else if (normalizedText.includes(token)) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
158
189
|
};
|
|
159
|
-
const hasToken = (text, token) => normalize(text).split(' ').includes(token);
|
|
160
190
|
const optionIsSelected = (node) => {
|
|
161
191
|
if (!(node instanceof HTMLElement)) return false;
|
|
162
192
|
const ariaChecked = node.getAttribute('aria-checked');
|
|
@@ -180,9 +210,28 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
180
210
|
);
|
|
181
211
|
} catch {}
|
|
182
212
|
};
|
|
213
|
+
const dispatchHoverSequence = (target) => {
|
|
214
|
+
if (!target || !(target instanceof EventTarget)) return false;
|
|
215
|
+
const types = ['pointerover', 'pointerenter', 'mouseover', 'mouseenter', 'pointermove', 'mousemove'];
|
|
216
|
+
for (const type of types) {
|
|
217
|
+
try {
|
|
218
|
+
const common = { bubbles: true, cancelable: true, view: window };
|
|
219
|
+
const event =
|
|
220
|
+
type.startsWith('pointer') && 'PointerEvent' in window
|
|
221
|
+
? new PointerEvent(type, { ...common, pointerId: 1, pointerType: 'mouse' })
|
|
222
|
+
: new MouseEvent(type, common);
|
|
223
|
+
target.dispatchEvent(event);
|
|
224
|
+
} catch {}
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
target.focus?.();
|
|
228
|
+
} catch {}
|
|
229
|
+
return true;
|
|
230
|
+
};
|
|
183
231
|
|
|
184
232
|
const TRAILING_SELECTOR = '[data-model-picker-thinking-effort-action="true"]';
|
|
185
233
|
const INTELLIGENCE_MENU_SELECTOR = '[data-testid="composer-intelligence-picker-content"]';
|
|
234
|
+
const PRO_EFFORT_TRIGGER_SELECTOR = '[data-testid="composer-intelligence-pro-thinking-effort-trigger"]';
|
|
186
235
|
|
|
187
236
|
const findModelButton = () => document.querySelector(MODEL_BUTTON_SELECTOR);
|
|
188
237
|
const findTrailingButtons = () => Array.from(document.querySelectorAll(TRAILING_SELECTOR));
|
|
@@ -270,13 +319,57 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
270
319
|
return { error: redactDiagnosticText(err && err.message ? err.message : err) };
|
|
271
320
|
}
|
|
272
321
|
};
|
|
322
|
+
const modelKindFromNode = (button) => {
|
|
323
|
+
const label = normalize(
|
|
324
|
+
(button?.textContent ?? '') + ' ' + (button?.getAttribute?.('aria-label') ?? ''),
|
|
325
|
+
);
|
|
326
|
+
if (hasToken(label, 'pro')) return 'pro';
|
|
327
|
+
if (hasToken(label, 'thinking')) return 'thinking';
|
|
328
|
+
if (hasToken(label, 'instant')) return 'instant';
|
|
329
|
+
return null;
|
|
330
|
+
};
|
|
331
|
+
const currentModelKind = () => modelKindFromNode(findModelButton());
|
|
332
|
+
const effectiveTargetModelKind = () => TARGET_MODEL_KIND || currentModelKind();
|
|
333
|
+
const isIntelligenceEffortMenu = (menu) => {
|
|
334
|
+
if (menu?.getAttribute?.('data-testid') === 'composer-intelligence-picker-content') {
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
const label = menu?.querySelector?.('.__menu-label, [class*="menu-label"]');
|
|
338
|
+
return normalize(label?.textContent ?? '').includes('intelligence');
|
|
339
|
+
};
|
|
273
340
|
const failure = (status, extra = {}) => ({
|
|
274
341
|
status,
|
|
342
|
+
modelKind: effectiveTargetModelKind(),
|
|
275
343
|
...extra,
|
|
276
344
|
diagnostic: collectPickerDiagnostic(),
|
|
277
345
|
});
|
|
278
|
-
const findOptionInMenu = (menu) => {
|
|
279
|
-
|
|
346
|
+
const findOptionInMenu = (menu, modelKindOverride = null) => {
|
|
347
|
+
const items = Array.from(menu.querySelectorAll(MENU_ITEM_SELECTOR));
|
|
348
|
+
const modelKind = modelKindOverride || effectiveTargetModelKind();
|
|
349
|
+
if (modelKind === 'pro') {
|
|
350
|
+
for (const item of items) {
|
|
351
|
+
const itemText = normalize(
|
|
352
|
+
(item.textContent ?? '') + ' ' + (item.getAttribute?.('aria-label') ?? ''),
|
|
353
|
+
);
|
|
354
|
+
if (
|
|
355
|
+
hasToken(itemText, 'pro') &&
|
|
356
|
+
(matchesLevel(item.textContent ?? '') ||
|
|
357
|
+
matchesLevel(item.getAttribute?.('aria-label') ?? ''))
|
|
358
|
+
) {
|
|
359
|
+
return item;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (isIntelligenceEffortMenu(menu)) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const item of items) {
|
|
367
|
+
const itemText = normalize(
|
|
368
|
+
(item.textContent ?? '') + ' ' + (item.getAttribute?.('aria-label') ?? ''),
|
|
369
|
+
);
|
|
370
|
+
if (modelKind && modelKind !== 'pro' && hasToken(itemText, 'pro')) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
280
373
|
if (
|
|
281
374
|
matchesLevel(item.textContent ?? '') ||
|
|
282
375
|
matchesLevel(item.getAttribute?.('aria-label') ?? '')
|
|
@@ -296,14 +389,21 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
296
389
|
};
|
|
297
390
|
const isEffortMenu = (menu) => {
|
|
298
391
|
if (!isVisible(menu)) return false;
|
|
392
|
+
if (menu.getAttribute?.('data-testid') === 'composer-intelligence-picker-content') return true;
|
|
299
393
|
const label = menu.querySelector?.('.__menu-label, [class*="menu-label"]');
|
|
300
394
|
const labelText = normalize(label?.textContent ?? '');
|
|
301
395
|
return (
|
|
396
|
+
labelText.includes('intelligence') ||
|
|
302
397
|
labelText.includes('thinking time') ||
|
|
303
398
|
labelText.includes('thinking effort') ||
|
|
304
399
|
countEffortLevels(menu) >= 2
|
|
305
400
|
);
|
|
306
401
|
};
|
|
402
|
+
const isProEffortMenu = (menu) => {
|
|
403
|
+
if (!isVisible(menu)) return false;
|
|
404
|
+
const text = normalize(menu?.textContent ?? '');
|
|
405
|
+
return text.includes('pro standard') && text.includes('pro extended');
|
|
406
|
+
};
|
|
307
407
|
const controlledMenu = (trigger) => {
|
|
308
408
|
const id = trigger?.getAttribute?.('aria-controls');
|
|
309
409
|
if (!id) return null;
|
|
@@ -318,9 +418,69 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
318
418
|
}
|
|
319
419
|
return null;
|
|
320
420
|
};
|
|
321
|
-
const
|
|
421
|
+
const controlledProEffortMenu = (trigger) => {
|
|
422
|
+
const id = trigger?.getAttribute?.('aria-controls');
|
|
423
|
+
if (!id) return null;
|
|
424
|
+
const menu = document.getElementById?.(id);
|
|
425
|
+
return isProEffortMenu(menu) ? menu : null;
|
|
426
|
+
};
|
|
427
|
+
const findVisibleProEffortMenu = (trigger) => {
|
|
428
|
+
const controlled = controlledProEffortMenu(trigger);
|
|
429
|
+
if (controlled) return controlled;
|
|
430
|
+
for (const menu of document.querySelectorAll(MENU_CONTAINER_SELECTOR)) {
|
|
431
|
+
if (isProEffortMenu(menu)) return menu;
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
434
|
+
};
|
|
435
|
+
const matchesProEffortLevel = (node) => {
|
|
436
|
+
const text = normalize(
|
|
437
|
+
(node?.textContent ?? '') + ' ' + (node?.getAttribute?.('aria-label') ?? ''),
|
|
438
|
+
);
|
|
439
|
+
if (TARGET_LEVEL === 'standard') {
|
|
440
|
+
return text.includes('pro') && text.includes('standard');
|
|
441
|
+
}
|
|
442
|
+
if (TARGET_LEVEL === 'extended') {
|
|
443
|
+
return text.includes('pro') && text.includes('extended');
|
|
444
|
+
}
|
|
445
|
+
return false;
|
|
446
|
+
};
|
|
447
|
+
const findProEffortOptionInMenu = (menu) => {
|
|
448
|
+
for (const item of menu.querySelectorAll(MENU_ITEM_SELECTOR)) {
|
|
449
|
+
if (matchesProEffortLevel(item)) return item;
|
|
450
|
+
}
|
|
451
|
+
return null;
|
|
452
|
+
};
|
|
453
|
+
const currentProEffortPillMatchesTarget = (trigger, modelKindOverride = null) => {
|
|
454
|
+
const button = trigger?.matches?.('button.__composer-pill') ? trigger : findModelButton();
|
|
455
|
+
if ((modelKindOverride || TARGET_MODEL_KIND || modelKindFromNode(button)) !== 'pro') {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
const label = normalize(button?.textContent ?? '');
|
|
459
|
+
if (TARGET_LEVEL === 'standard') {
|
|
460
|
+
return hasToken(label, 'pro') && !hasToken(label, 'extended');
|
|
461
|
+
}
|
|
462
|
+
if (TARGET_LEVEL === 'extended') {
|
|
463
|
+
return hasToken(label, 'pro') && hasToken(label, 'extended');
|
|
464
|
+
}
|
|
465
|
+
return false;
|
|
466
|
+
};
|
|
467
|
+
const currentEffortPillMatchesTarget = (trigger, modelKindOverride = null) => {
|
|
468
|
+
if (currentProEffortPillMatchesTarget(trigger, modelKindOverride)) return true;
|
|
469
|
+
const button = trigger?.matches?.('button.__composer-pill') ? trigger : findModelButton();
|
|
470
|
+
if ((modelKindOverride || TARGET_MODEL_KIND || modelKindFromNode(button)) === 'pro') {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
const label = (button?.textContent ?? '') + ' ' + (button?.getAttribute?.('aria-label') ?? '');
|
|
474
|
+
return matchesLevel(label);
|
|
475
|
+
};
|
|
476
|
+
const selectAndVerify = async (trigger, findOption, modelKindOverride = null) => {
|
|
322
477
|
const option = findOption();
|
|
323
|
-
|
|
478
|
+
const triggerModelKind =
|
|
479
|
+
modelKindOverride ||
|
|
480
|
+
TARGET_MODEL_KIND ||
|
|
481
|
+
modelKindFromNode(trigger) ||
|
|
482
|
+
effectiveTargetModelKind();
|
|
483
|
+
if (!option) return failure('option-not-found', { modelKind: triggerModelKind });
|
|
324
484
|
const label = option.textContent?.trim?.() || null;
|
|
325
485
|
if (optionIsSelected(option)) {
|
|
326
486
|
closeOpenMenus();
|
|
@@ -334,6 +494,10 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
334
494
|
closeOpenMenus();
|
|
335
495
|
return { status: 'switched', label: refreshed.textContent?.trim?.() || label };
|
|
336
496
|
}
|
|
497
|
+
if (currentEffortPillMatchesTarget(trigger, triggerModelKind)) {
|
|
498
|
+
closeOpenMenus();
|
|
499
|
+
return { status: 'switched', label };
|
|
500
|
+
}
|
|
337
501
|
|
|
338
502
|
if (!refreshed && trigger && trigger.getAttribute?.('aria-expanded') !== 'true') {
|
|
339
503
|
dispatchClickSequence(trigger);
|
|
@@ -346,29 +510,56 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
346
510
|
closeOpenMenus();
|
|
347
511
|
return { status: 'switched', label: selected.textContent?.trim?.() || label };
|
|
348
512
|
}
|
|
513
|
+
if (currentEffortPillMatchesTarget(trigger, triggerModelKind)) {
|
|
514
|
+
closeOpenMenus();
|
|
515
|
+
return { status: 'switched', label };
|
|
516
|
+
}
|
|
349
517
|
await sleep(100);
|
|
350
518
|
}
|
|
351
|
-
const result = failure('selection-unverified');
|
|
519
|
+
const result = failure('selection-unverified', { modelKind: triggerModelKind });
|
|
352
520
|
closeOpenMenus();
|
|
353
521
|
return result;
|
|
354
522
|
};
|
|
523
|
+
const selectProEffortFromSubmenu = async () => {
|
|
524
|
+
if (TARGET_MODEL_KIND !== 'pro' || (TARGET_LEVEL !== 'standard' && TARGET_LEVEL !== 'extended')) {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
const trigger = document.querySelector(PRO_EFFORT_TRIGGER_SELECTOR);
|
|
528
|
+
if (!trigger) {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
dispatchHoverSequence(trigger);
|
|
532
|
+
if (trigger.getAttribute?.('aria-expanded') !== 'true') {
|
|
533
|
+
dispatchClickSequence(trigger);
|
|
534
|
+
}
|
|
535
|
+
const deadline = performance.now() + MAX_WAIT_MS;
|
|
536
|
+
while (performance.now() < deadline) {
|
|
537
|
+
const menu = findVisibleProEffortMenu(trigger);
|
|
538
|
+
if (menu) {
|
|
539
|
+
return selectAndVerify(trigger, () => {
|
|
540
|
+
const currentMenu = findVisibleProEffortMenu(trigger);
|
|
541
|
+
return currentMenu ? findProEffortOptionInMenu(currentMenu) : null;
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
await sleep(100);
|
|
545
|
+
}
|
|
546
|
+
return null;
|
|
547
|
+
};
|
|
355
548
|
|
|
356
549
|
// Current ChatGPT exposes a standalone Pro or Thinking composer pill whose
|
|
357
550
|
// controlled menu contains the effort levels. Prefer this ownership boundary
|
|
358
551
|
// before probing older model-picker layouts.
|
|
359
552
|
const COMPOSER_EFFORT_PILL_SELECTORS = [
|
|
360
|
-
'form button.__composer-pill
|
|
361
|
-
'[data-testid="composer-footer-actions"] button
|
|
362
|
-
'.__composer-pill-composite button
|
|
553
|
+
'form button.__composer-pill',
|
|
554
|
+
'[data-testid="composer-footer-actions"] button.__composer-pill',
|
|
555
|
+
'.__composer-pill-composite button.__composer-pill',
|
|
363
556
|
];
|
|
364
557
|
const findComposerEffortPill = () => {
|
|
365
|
-
const candidates = [];
|
|
366
558
|
const seen = new Set();
|
|
367
559
|
for (const selector of COMPOSER_EFFORT_PILL_SELECTORS) {
|
|
368
560
|
for (const button of document.querySelectorAll(selector)) {
|
|
369
561
|
if (seen.has(button) || !isVisible(button)) continue;
|
|
370
562
|
seen.add(button);
|
|
371
|
-
if (button.getAttribute?.('aria-haspopup') !== 'menu') continue;
|
|
372
563
|
if (button.getAttribute?.('data-testid') === 'model-switcher-dropdown-button') continue;
|
|
373
564
|
const label = normalize(
|
|
374
565
|
(button.getAttribute?.('aria-label') ?? '') + ' ' +
|
|
@@ -378,21 +569,19 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
378
569
|
if (
|
|
379
570
|
(TARGET_MODEL_KIND === 'pro' && hasToken(label, 'pro') && !hasToken(label, 'thinking')) ||
|
|
380
571
|
(TARGET_MODEL_KIND === 'thinking' && hasToken(label, 'thinking') && !hasToken(label, 'pro')) ||
|
|
381
|
-
(!TARGET_MODEL_KIND && hasToken(label, 'thinking'))
|
|
572
|
+
(!TARGET_MODEL_KIND && hasToken(label, 'thinking')) ||
|
|
573
|
+
(button.matches?.('button.__composer-pill') && matchesAnyEffortLevel(label))
|
|
382
574
|
) {
|
|
383
575
|
return button;
|
|
384
576
|
}
|
|
385
|
-
candidates.push(button);
|
|
386
577
|
}
|
|
387
578
|
}
|
|
388
|
-
|
|
389
|
-
button.matches?.('button.__composer-pill'),
|
|
390
|
-
);
|
|
391
|
-
return composerPills.length === 1 ? composerPills[0] : null;
|
|
579
|
+
return null;
|
|
392
580
|
};
|
|
393
581
|
|
|
394
582
|
const composerEffortPill = findComposerEffortPill();
|
|
395
583
|
if (composerEffortPill) {
|
|
584
|
+
const composerModelKind = TARGET_MODEL_KIND || modelKindFromNode(composerEffortPill);
|
|
396
585
|
if (composerEffortPill.getAttribute?.('aria-expanded') !== 'true') {
|
|
397
586
|
dispatchClickSequence(composerEffortPill);
|
|
398
587
|
await sleep(INITIAL_WAIT_MS);
|
|
@@ -401,14 +590,24 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
401
590
|
while (performance.now() < deadline) {
|
|
402
591
|
const menu = findVisibleEffortMenu(composerEffortPill);
|
|
403
592
|
if (menu) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
return
|
|
407
|
-
}
|
|
593
|
+
const proEffortResult = await selectProEffortFromSubmenu();
|
|
594
|
+
if (proEffortResult) {
|
|
595
|
+
return proEffortResult;
|
|
596
|
+
}
|
|
597
|
+
return selectAndVerify(
|
|
598
|
+
composerEffortPill,
|
|
599
|
+
() => {
|
|
600
|
+
const currentMenu = findVisibleEffortMenu(composerEffortPill);
|
|
601
|
+
return currentMenu ? findOptionInMenu(currentMenu, composerModelKind) : null;
|
|
602
|
+
},
|
|
603
|
+
composerModelKind,
|
|
604
|
+
);
|
|
408
605
|
}
|
|
409
606
|
await sleep(100);
|
|
410
607
|
}
|
|
411
|
-
const result = failure('menu-not-found'
|
|
608
|
+
const result = failure('menu-not-found', {
|
|
609
|
+
modelKind: composerModelKind,
|
|
610
|
+
});
|
|
412
611
|
closeOpenMenus();
|
|
413
612
|
return result;
|
|
414
613
|
}
|
|
@@ -512,15 +711,9 @@ function buildThinkingTimeExpression(level, desiredModel) {
|
|
|
512
711
|
// ---------- COMPATIBILITY UI: unified "Intelligence" effort picker ----------
|
|
513
712
|
// One observed ChatGPT layout replaced the per-model trailing buttons with a single
|
|
514
713
|
// "Intelligence" menu ([data-testid="composer-intelligence-picker-content"]),
|
|
515
|
-
// whose role="menuitemradio" rows are the effort tiers.
|
|
516
|
-
//
|
|
517
|
-
//
|
|
518
|
-
// [data-testid="composer-intelligence-pro-thinking-effort-trigger"]. We
|
|
519
|
-
// confirm Pro Extended by the radio's checked state (real proof), never by
|
|
520
|
-
// the composer-pill label. The new non-pro tiers (Instant/Medium/High/Extra
|
|
521
|
-
// High) don't map cleanly onto light/standard/extended/heavy, so we only
|
|
522
|
-
// drive this picker for the strict Pro Extended target and let other levels
|
|
523
|
-
// fall through to the legacy paths below.
|
|
714
|
+
// whose role="menuitemradio" rows are the effort tiers. We verify the checked
|
|
715
|
+
// radio instead of trusting the composer-pill label; non-Pro targets also
|
|
716
|
+
// explicitly skip Pro rows before matching effort labels.
|
|
524
717
|
if (TARGET_MODEL_KIND === 'pro' && TARGET_LEVEL === 'extended') {
|
|
525
718
|
const matchesProExtended = (node) => {
|
|
526
719
|
const text = normalize(
|