@steipete/oracle 0.14.0 → 0.15.0
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 +10 -8
- package/dist/docs-site/browser-mode.html +3 -2
- package/dist/docs-site/cli-reference.html +1 -1
- package/dist/docs-site/mcp.html +1 -1
- package/dist/src/browser/actions/assistantResponse.js +2 -1
- package/dist/src/browser/actions/deepResearch.js +132 -61
- package/dist/src/browser/actions/modelSelection.js +388 -30
- package/dist/src/browser/actions/thinkingTime.js +303 -65
- package/dist/src/browser/artifacts.js +2 -8
- package/dist/src/browser/chatgptFiles.js +198 -49
- package/dist/src/browser/chatgptImages.js +126 -24
- package/dist/src/browser/chromeLifecycle.js +35 -4
- package/dist/src/browser/deepResearchResult.js +23 -0
- package/dist/src/browser/index.js +145 -19
- package/dist/src/browser/profileCopy.js +93 -0
- package/dist/src/browser/projectSourcesRunner.js +2 -1
- package/dist/src/browser/prompt.js +151 -22
- package/dist/src/cli/browserConfig.js +19 -2
- package/dist/src/cli/browserDefaults.js +2 -1
- package/dist/src/cli/options.js +8 -0
- package/dist/src/cli/sessionRunner.js +13 -7
- 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/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/package.json +6 -6
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
|
@@ -12,8 +12,7 @@ export async function ensureModelSelection(Runtime, desiredModel, logger, strate
|
|
|
12
12
|
const result = outcome.result?.value;
|
|
13
13
|
switch (result?.status) {
|
|
14
14
|
case "already-selected":
|
|
15
|
-
case "switched":
|
|
16
|
-
case "switched-best-effort": {
|
|
15
|
+
case "switched": {
|
|
17
16
|
const label = result.label?.trim() || (strategy === "current" ? null : desiredModel);
|
|
18
17
|
if (strategy !== "current") {
|
|
19
18
|
assertResolvedModelSelection(desiredModel, label ?? desiredModel);
|
|
@@ -95,8 +94,8 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
95
94
|
const composerIncludesLiteral = JSON.stringify(composerSignalMatchers.includesAny);
|
|
96
95
|
const composerExcludesLiteral = JSON.stringify(composerSignalMatchers.excludesAny);
|
|
97
96
|
const composerAllowBlankLiteral = JSON.stringify(composerSignalMatchers.allowBlank);
|
|
98
|
-
const menuContainerLiteral = JSON.stringify(MENU_CONTAINER_SELECTOR);
|
|
99
|
-
const menuItemLiteral = JSON.stringify(MENU_ITEM_SELECTOR);
|
|
97
|
+
const menuContainerLiteral = JSON.stringify(`${MENU_CONTAINER_SELECTOR}, [role="listbox"], [role="dialog"]`);
|
|
98
|
+
const menuItemLiteral = JSON.stringify(`${MENU_ITEM_SELECTOR}, [role="option"], [role="radio"], [role="combobox"]`);
|
|
100
99
|
return `(() => {
|
|
101
100
|
${buildClickDispatcher()}
|
|
102
101
|
// Capture the selectors and matcher literals up front so the browser expression stays pure.
|
|
@@ -134,6 +133,8 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
134
133
|
? '5-4'
|
|
135
134
|
: normalizedTarget.includes('5 5')
|
|
136
135
|
? '5-5'
|
|
136
|
+
: normalizedTarget.includes('5 3')
|
|
137
|
+
? '5-3'
|
|
137
138
|
: normalizedTarget.includes('5 2')
|
|
138
139
|
? '5-2'
|
|
139
140
|
: normalizedTarget.includes('5 1')
|
|
@@ -196,7 +197,19 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
196
197
|
);
|
|
197
198
|
if (!label) return false;
|
|
198
199
|
if (label.includes('click to remove')) return false;
|
|
199
|
-
const modelTokens = [
|
|
200
|
+
const modelTokens = [
|
|
201
|
+
'chatgpt',
|
|
202
|
+
'gpt',
|
|
203
|
+
'instant',
|
|
204
|
+
'thinking',
|
|
205
|
+
'pro',
|
|
206
|
+
'extended',
|
|
207
|
+
'standard',
|
|
208
|
+
'medium',
|
|
209
|
+
'high',
|
|
210
|
+
'heavy',
|
|
211
|
+
'light',
|
|
212
|
+
];
|
|
200
213
|
return modelTokens.some((token) => hasToken(label, token));
|
|
201
214
|
};
|
|
202
215
|
const findModelButton = () => {
|
|
@@ -206,6 +219,14 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
206
219
|
};
|
|
207
220
|
|
|
208
221
|
const closeMenu = () => {
|
|
222
|
+
const dialogCloseButton = document.querySelector(
|
|
223
|
+
'[role="dialog"] [data-testid="close-button"]',
|
|
224
|
+
);
|
|
225
|
+
if (dialogCloseButton) {
|
|
226
|
+
try {
|
|
227
|
+
if (dispatchClickSequence(dialogCloseButton)) return;
|
|
228
|
+
} catch {}
|
|
229
|
+
}
|
|
209
230
|
const button = findModelButton();
|
|
210
231
|
if (!button) return;
|
|
211
232
|
try {
|
|
@@ -231,6 +252,25 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
231
252
|
const getComposerModelLabel = () =>
|
|
232
253
|
(document.querySelector(COMPOSER_MODEL_SIGNAL_SELECTOR)?.textContent ?? '').trim();
|
|
233
254
|
const readComposerModelSignal = () => normalizeText(getComposerModelLabel());
|
|
255
|
+
const isIntelligenceEffortLabel = (label) =>
|
|
256
|
+
label === 'instant' ||
|
|
257
|
+
label === 'medium' ||
|
|
258
|
+
label === 'high' ||
|
|
259
|
+
label === 'extra high' ||
|
|
260
|
+
label === 'extended' ||
|
|
261
|
+
label === 'standard' ||
|
|
262
|
+
label === 'heavy' ||
|
|
263
|
+
label === 'light';
|
|
264
|
+
const formatModelOptionLabel = (label) => {
|
|
265
|
+
const normalized = normalizeText(label ?? '');
|
|
266
|
+
if (normalized === '5 5' || normalized === 'gpt 5 5') return 'GPT-5.5';
|
|
267
|
+
if (normalized === '5 4' || normalized === 'gpt 5 4') return 'GPT-5.4';
|
|
268
|
+
if (normalized === '5 3' || normalized === 'gpt 5 3') return 'GPT-5.3';
|
|
269
|
+
if (normalized === '5 2' || normalized === 'gpt 5 2') return 'GPT-5.2';
|
|
270
|
+
if (normalized === '5 1' || normalized === 'gpt 5 1') return 'GPT-5.1';
|
|
271
|
+
if (normalized === '5 0' || normalized === 'gpt 5 0') return 'GPT-5.0';
|
|
272
|
+
return label || '';
|
|
273
|
+
};
|
|
234
274
|
const withProPillSignal = (label) => {
|
|
235
275
|
const resolved = label || '';
|
|
236
276
|
if (!wantsPro || !hasProComposerPill()) return resolved;
|
|
@@ -240,10 +280,84 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
240
280
|
if (normalized.includes('pro')) return resolved;
|
|
241
281
|
return resolved + ' + Pro';
|
|
242
282
|
};
|
|
243
|
-
const getResolvedLabel = (fallback) =>
|
|
244
|
-
withProPillSignal(getComposerModelLabel() || getButtonLabel() || fallback);
|
|
245
283
|
const isThinkingEffortLabel = (label) =>
|
|
246
|
-
label === 'extended' ||
|
|
284
|
+
label === 'extended' ||
|
|
285
|
+
label === 'standard' ||
|
|
286
|
+
label === 'heavy' ||
|
|
287
|
+
label === 'light' ||
|
|
288
|
+
label === 'medium' ||
|
|
289
|
+
label === 'high' ||
|
|
290
|
+
label === 'extra high';
|
|
291
|
+
const isNonProIntelligenceThinkingLabel = (label) =>
|
|
292
|
+
label === 'medium' || label === 'high' || label === 'extra high';
|
|
293
|
+
const scoreNonProGpt55ThinkingLabel = (label) => {
|
|
294
|
+
if (label === 'extra high') return 1400;
|
|
295
|
+
if (label === 'high') return 1200;
|
|
296
|
+
return 1000;
|
|
297
|
+
};
|
|
298
|
+
const versionFromLabel = (label) => {
|
|
299
|
+
if (label === '5 5' || label === 'gpt 5 5') return '5-5';
|
|
300
|
+
if (label === '5 4' || label === 'gpt 5 4') return '5-4';
|
|
301
|
+
if (label === '5 3' || label === 'gpt 5 3') return '5-3';
|
|
302
|
+
if (label === '5 2' || label === 'gpt 5 2') return '5-2';
|
|
303
|
+
if (label === '5 1' || label === 'gpt 5 1') return '5-1';
|
|
304
|
+
if (label === '5 0' || label === 'gpt 5 0') return '5-0';
|
|
305
|
+
if (label === '4 5' || label === 'gpt 4 5') return '4-5';
|
|
306
|
+
return null;
|
|
307
|
+
};
|
|
308
|
+
const versionFromTestId = (testid) => {
|
|
309
|
+
const normalized = normalizeText(testid);
|
|
310
|
+
if (normalized.includes('5 5') || normalized.includes('gpt55')) return '5-5';
|
|
311
|
+
if (normalized.includes('5 4') || normalized.includes('gpt54')) return '5-4';
|
|
312
|
+
if (normalized.includes('5 3') || normalized.includes('gpt53')) return '5-3';
|
|
313
|
+
if (normalized.includes('5 2') || normalized.includes('gpt52')) return '5-2';
|
|
314
|
+
if (normalized.includes('5 1') || normalized.includes('gpt51')) return '5-1';
|
|
315
|
+
if (normalized.includes('5 0') || normalized.includes('gpt50')) return '5-0';
|
|
316
|
+
return null;
|
|
317
|
+
};
|
|
318
|
+
const getConfigurationDialog = () => document.querySelector('[role="dialog"]');
|
|
319
|
+
const getConfiguredVersionLabel = () =>
|
|
320
|
+
(getConfigurationDialog()
|
|
321
|
+
?.querySelector?.('[role="combobox"][aria-labelledby="model-selection-label"]')
|
|
322
|
+
?.textContent ?? '').trim();
|
|
323
|
+
const getConfiguredVariantLabel = () => {
|
|
324
|
+
const label =
|
|
325
|
+
(getConfigurationDialog()
|
|
326
|
+
?.querySelector?.('[aria-label="Model options"] [role="radio"][aria-checked="true"]')
|
|
327
|
+
?.textContent ?? '').trim();
|
|
328
|
+
const normalized = normalizeText(label);
|
|
329
|
+
if (normalized.startsWith('instant')) return 'Instant';
|
|
330
|
+
if (normalized.startsWith('thinking')) return 'Thinking';
|
|
331
|
+
if (normalized.startsWith('pro')) return 'Pro';
|
|
332
|
+
return label;
|
|
333
|
+
};
|
|
334
|
+
const configuredSelectionMatchesTarget = () => {
|
|
335
|
+
const configuredVersion = versionFromLabel(normalizeText(getConfiguredVersionLabel()));
|
|
336
|
+
if (!configuredVersion || configuredVersion !== desiredVersion) return false;
|
|
337
|
+
const configuredVariant = normalizeText(getConfiguredVariantLabel());
|
|
338
|
+
if (wantsPro) return labelHasProWord(configuredVariant);
|
|
339
|
+
if (wantsInstant) return configuredVariant.includes('instant');
|
|
340
|
+
if (wantsThinking) {
|
|
341
|
+
return configuredVariant.includes('thinking') && !labelHasProWord(configuredVariant);
|
|
342
|
+
}
|
|
343
|
+
return true;
|
|
344
|
+
};
|
|
345
|
+
const getResolvedLabel = (fallback) => {
|
|
346
|
+
if (configuredSelectionMatchesTarget()) {
|
|
347
|
+
const variant = getConfiguredVariantLabel();
|
|
348
|
+
const version = formatModelOptionLabel(getConfiguredVersionLabel());
|
|
349
|
+
return [variant, version].filter(Boolean).join(' ');
|
|
350
|
+
}
|
|
351
|
+
const composerLabel = getComposerModelLabel();
|
|
352
|
+
if (composerLabel) return withProPillSignal(composerLabel);
|
|
353
|
+
const buttonLabel = getButtonLabel();
|
|
354
|
+
const normalizedButton = normalizeText(buttonLabel);
|
|
355
|
+
const fallbackLabel = formatModelOptionLabel(fallback);
|
|
356
|
+
if (fallbackLabel && !wantsPro && isIntelligenceEffortLabel(normalizedButton)) {
|
|
357
|
+
return fallbackLabel;
|
|
358
|
+
}
|
|
359
|
+
return withProPillSignal(buttonLabel || fallbackLabel || fallback);
|
|
360
|
+
};
|
|
247
361
|
if (MODEL_STRATEGY === 'current') {
|
|
248
362
|
const currentLabel = getResolvedLabel('') || null;
|
|
249
363
|
return {
|
|
@@ -257,15 +371,18 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
257
371
|
return { status: 'button-missing' };
|
|
258
372
|
}
|
|
259
373
|
const buttonMatchesTarget = () => {
|
|
374
|
+
if (configuredSelectionMatchesTarget()) return true;
|
|
260
375
|
const normalizedLabel = normalizeText(getButtonLabel());
|
|
261
376
|
if (!normalizedLabel) return false;
|
|
377
|
+
if (wantsThinking && !wantsPro && hasProComposerPill()) return false;
|
|
262
378
|
if (isTargetGpt55VisibleAlias(normalizedLabel)) return true;
|
|
263
379
|
if (
|
|
264
380
|
wantsThinking &&
|
|
265
381
|
desiredVersion === '5-5' &&
|
|
266
382
|
!hasProComposerPill() &&
|
|
267
383
|
isThinkingEffortLabel(normalizedLabel) &&
|
|
268
|
-
|
|
384
|
+
(isNonProIntelligenceThinkingLabel(normalizedLabel) ||
|
|
385
|
+
isTargetGpt55VisibleAlias(readComposerModelSignal()))
|
|
269
386
|
) {
|
|
270
387
|
return true;
|
|
271
388
|
}
|
|
@@ -283,6 +400,7 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
283
400
|
if (desiredVersion) {
|
|
284
401
|
if (desiredVersion === '5-5' && !normalizedLabel.includes('5 5')) return false;
|
|
285
402
|
if (desiredVersion === '5-4' && !normalizedLabel.includes('5 4')) return false;
|
|
403
|
+
if (desiredVersion === '5-3' && !normalizedLabel.includes('5 3')) return false;
|
|
286
404
|
if (desiredVersion === '5-2' && !normalizedLabel.includes('5 2')) return false;
|
|
287
405
|
if (desiredVersion === '5-1' && !normalizedLabel.includes('5 1')) return false;
|
|
288
406
|
if (desiredVersion === '5-0' && !normalizedLabel.includes('5 0')) return false;
|
|
@@ -292,9 +410,9 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
292
410
|
if (wantsInstant && !normalizedLabel.includes('instant')) return false;
|
|
293
411
|
if (
|
|
294
412
|
wantsThinking &&
|
|
295
|
-
desiredVersion
|
|
296
|
-
|
|
297
|
-
!normalizedLabel
|
|
413
|
+
desiredVersion &&
|
|
414
|
+
versionFromLabel(normalizedLabel) === desiredVersion &&
|
|
415
|
+
!labelHasProWord(normalizedLabel)
|
|
298
416
|
) {
|
|
299
417
|
return true;
|
|
300
418
|
}
|
|
@@ -359,10 +477,21 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
359
477
|
};
|
|
360
478
|
|
|
361
479
|
const getOptionLabel = (node) => node?.textContent?.trim() ?? '';
|
|
362
|
-
const
|
|
480
|
+
const isDetachedProEffortMenu = (menu) => {
|
|
481
|
+
const text = normalizeText(menu?.textContent ?? '');
|
|
482
|
+
return (
|
|
483
|
+
menu?.getAttribute?.('data-testid') !== 'composer-intelligence-picker-content' &&
|
|
484
|
+
text.includes('pro standard') &&
|
|
485
|
+
text.includes('pro extended')
|
|
486
|
+
);
|
|
487
|
+
};
|
|
488
|
+
const isNestedEffortControl = (node, menu) =>
|
|
363
489
|
node instanceof HTMLElement &&
|
|
364
490
|
(node.getAttribute('data-model-picker-thinking-effort-action') === 'true' ||
|
|
365
|
-
|
|
491
|
+
node.getAttribute('data-composer-intelligence-pro-effort-action') === 'true' ||
|
|
492
|
+
Boolean(node.closest('[data-model-picker-thinking-effort-action="true"]')) ||
|
|
493
|
+
Boolean(node.closest('[data-composer-intelligence-pro-effort-action="true"]')) ||
|
|
494
|
+
isDetachedProEffortMenu(menu));
|
|
366
495
|
const optionIsSelected = (node) => {
|
|
367
496
|
if (!(node instanceof HTMLElement)) {
|
|
368
497
|
return false;
|
|
@@ -382,13 +511,28 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
382
511
|
return false;
|
|
383
512
|
};
|
|
384
513
|
|
|
385
|
-
const scoreOption = (normalizedText, testid) => {
|
|
514
|
+
const scoreOption = (normalizedText, testid, node) => {
|
|
386
515
|
// Assign a score to every node so we can pick the most likely match without brittle equality checks.
|
|
387
516
|
if (!normalizedText && !testid) {
|
|
388
517
|
return 0;
|
|
389
518
|
}
|
|
390
519
|
let score = 0;
|
|
391
520
|
const normalizedTestId = (testid ?? '').toLowerCase();
|
|
521
|
+
const candidateTextVersion = versionFromLabel(normalizedText);
|
|
522
|
+
const candidateIsVersionCombobox =
|
|
523
|
+
node?.getAttribute?.('role') === 'combobox' &&
|
|
524
|
+
node?.getAttribute?.('aria-labelledby') === 'model-selection-label';
|
|
525
|
+
if (candidateIsVersionCombobox && candidateTextVersion === desiredVersion) {
|
|
526
|
+
return 0;
|
|
527
|
+
}
|
|
528
|
+
const candidateOpensConfiguration =
|
|
529
|
+
Boolean(desiredVersion) &&
|
|
530
|
+
desiredVersion !== '5-5' &&
|
|
531
|
+
(normalizedTestId === 'model-configure-modal' ||
|
|
532
|
+
(candidateIsVersionCombobox && node?.getAttribute?.('aria-expanded') !== 'true'));
|
|
533
|
+
if (candidateOpensConfiguration) {
|
|
534
|
+
return 2000;
|
|
535
|
+
}
|
|
392
536
|
let exactTestIdMatch = false;
|
|
393
537
|
if (normalizedTestId) {
|
|
394
538
|
if (desiredVersion) {
|
|
@@ -411,6 +555,12 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
411
555
|
normalizedTestId.includes('gpt-5-4') ||
|
|
412
556
|
normalizedTestId.includes('gpt-5.4') ||
|
|
413
557
|
normalizedTestId.includes('gpt54');
|
|
558
|
+
const has53 =
|
|
559
|
+
normalizedTestId.includes('5-3') ||
|
|
560
|
+
normalizedTestId.includes('5.3') ||
|
|
561
|
+
normalizedTestId.includes('gpt-5-3') ||
|
|
562
|
+
normalizedTestId.includes('gpt-5.3') ||
|
|
563
|
+
normalizedTestId.includes('gpt53');
|
|
414
564
|
const has51 =
|
|
415
565
|
normalizedTestId.includes('5-1') ||
|
|
416
566
|
normalizedTestId.includes('5.1') ||
|
|
@@ -423,7 +573,19 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
423
573
|
normalizedTestId.includes('gpt-5-0') ||
|
|
424
574
|
normalizedTestId.includes('gpt-5.0') ||
|
|
425
575
|
normalizedTestId.includes('gpt50');
|
|
426
|
-
const candidateVersion = has55
|
|
576
|
+
const candidateVersion = has55
|
|
577
|
+
? '5-5'
|
|
578
|
+
: has54
|
|
579
|
+
? '5-4'
|
|
580
|
+
: has53
|
|
581
|
+
? '5-3'
|
|
582
|
+
: has52
|
|
583
|
+
? '5-2'
|
|
584
|
+
: has51
|
|
585
|
+
? '5-1'
|
|
586
|
+
: has50
|
|
587
|
+
? '5-0'
|
|
588
|
+
: null;
|
|
427
589
|
// If a candidate advertises a different version, ignore it entirely.
|
|
428
590
|
if (candidateVersion && candidateVersion !== desiredVersion) {
|
|
429
591
|
return 0;
|
|
@@ -452,10 +614,44 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
452
614
|
}
|
|
453
615
|
}
|
|
454
616
|
const candidateGpt55VisibleAlias = isTargetGpt55VisibleAlias(normalizedText);
|
|
617
|
+
const candidateIsNonProThinkingEffort =
|
|
618
|
+
isNonProIntelligenceThinkingLabel(normalizedText) && !normalizedTestId.includes('pro');
|
|
619
|
+
const hasActiveProPill = hasProComposerPill();
|
|
620
|
+
const candidateIsNonProGpt55Thinking =
|
|
621
|
+
wantsThinking && desiredVersion === '5-5' && candidateIsNonProThinkingEffort;
|
|
622
|
+
const candidateClearsProForThinking =
|
|
623
|
+
wantsThinking && !wantsPro && hasActiveProPill && candidateIsNonProThinkingEffort;
|
|
624
|
+
const candidateOpensVersionSubmenu =
|
|
625
|
+
wantsThinking &&
|
|
626
|
+
desiredVersion !== '5-5' &&
|
|
627
|
+
normalizedText === 'gpt 5 5' &&
|
|
628
|
+
!normalizedTestId.includes('pro');
|
|
629
|
+
const candidateSelectsDesiredVersion =
|
|
630
|
+
Boolean(desiredVersion) && candidateTextVersion === desiredVersion;
|
|
631
|
+
if (
|
|
632
|
+
desiredVersion &&
|
|
633
|
+
candidateTextVersion &&
|
|
634
|
+
candidateTextVersion !== desiredVersion &&
|
|
635
|
+
!candidateOpensVersionSubmenu
|
|
636
|
+
) {
|
|
637
|
+
return 0;
|
|
638
|
+
}
|
|
639
|
+
const candidateIsGpt55ThinkingFamily =
|
|
640
|
+
wantsThinking &&
|
|
641
|
+
desiredVersion === '5-5' &&
|
|
642
|
+
(normalizedText === 'gpt 5 5' ||
|
|
643
|
+
normalizedText === '5 5' ||
|
|
644
|
+
normalizedTestId.includes('gpt-5-5') ||
|
|
645
|
+
normalizedTestId.includes('gpt-5.5') ||
|
|
646
|
+
normalizedTestId.includes('gpt55'));
|
|
455
647
|
const candidateHasThinking =
|
|
456
648
|
normalizedText.includes('thinking') ||
|
|
457
649
|
normalizedTestId.includes('thinking') ||
|
|
458
|
-
|
|
650
|
+
candidateIsNonProGpt55Thinking ||
|
|
651
|
+
candidateClearsProForThinking ||
|
|
652
|
+
candidateOpensVersionSubmenu ||
|
|
653
|
+
candidateIsGpt55ThinkingFamily ||
|
|
654
|
+
(wantsThinking && desiredVersion && (exactTestIdMatch || candidateSelectsDesiredVersion));
|
|
459
655
|
const candidateHasLegacyProVersion = labelHasLegacyProVersion(normalizedText);
|
|
460
656
|
const candidateHasPro =
|
|
461
657
|
labelHasProWord(normalizedText) ||
|
|
@@ -463,12 +659,29 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
463
659
|
normalizedTestId.includes('pro');
|
|
464
660
|
const candidateHasInstant =
|
|
465
661
|
normalizedText.includes('instant') || normalizedTestId.includes('instant');
|
|
662
|
+
const candidateSelectsConfiguredVersion =
|
|
663
|
+
Boolean(getConfigurationDialog()) &&
|
|
664
|
+
candidateSelectsDesiredVersion &&
|
|
665
|
+
(node?.getAttribute?.('role') === 'option' ||
|
|
666
|
+
node?.getAttribute?.('role') === 'menuitemradio');
|
|
667
|
+
const candidateOpensInstantSubmenu =
|
|
668
|
+
wantsInstant &&
|
|
669
|
+
candidateSelectsDesiredVersion &&
|
|
670
|
+
!candidateHasInstant &&
|
|
671
|
+
(normalizedTestId.includes('submenu') ||
|
|
672
|
+
node?.getAttribute?.('aria-haspopup') === 'menu' ||
|
|
673
|
+
node?.getAttribute?.('data-has-submenu') !== null);
|
|
466
674
|
if (wantsPro && candidateHasThinking) return 0;
|
|
467
|
-
if (wantsPro && candidateHasLegacyProVersion) return 0;
|
|
468
|
-
if (wantsPro && !candidateHasPro) return 0;
|
|
469
|
-
if (
|
|
675
|
+
if (wantsPro && candidateHasLegacyProVersion && !candidateSelectsDesiredVersion) return 0;
|
|
676
|
+
if (wantsPro && !candidateHasPro && !candidateSelectsDesiredVersion) return 0;
|
|
677
|
+
if (
|
|
678
|
+
wantsInstant &&
|
|
679
|
+
!candidateHasInstant &&
|
|
680
|
+
!candidateOpensInstantSubmenu &&
|
|
681
|
+
!candidateSelectsConfiguredVersion
|
|
682
|
+
) return 0;
|
|
470
683
|
if (wantsThinking && candidateHasPro) return 0;
|
|
471
|
-
if (wantsThinking && !candidateHasThinking) return 0;
|
|
684
|
+
if (wantsThinking && !candidateHasThinking && !candidateSelectsDesiredVersion) return 0;
|
|
472
685
|
if (desiredVersion === '5-5' && normalizedText && !candidateGpt55VisibleAlias) {
|
|
473
686
|
const candidateHasVersion =
|
|
474
687
|
normalizedText.includes('5 5') ||
|
|
@@ -482,6 +695,43 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
482
695
|
if (candidateGpt55VisibleAlias) {
|
|
483
696
|
score += 900;
|
|
484
697
|
}
|
|
698
|
+
if (wantsPro && candidateHasPro && !candidateSelectsDesiredVersion) {
|
|
699
|
+
score += 900;
|
|
700
|
+
}
|
|
701
|
+
if (wantsInstant && candidateHasInstant && !candidateSelectsDesiredVersion) {
|
|
702
|
+
score += 900;
|
|
703
|
+
}
|
|
704
|
+
if (
|
|
705
|
+
wantsThinking &&
|
|
706
|
+
candidateHasThinking &&
|
|
707
|
+
!candidateSelectsDesiredVersion &&
|
|
708
|
+
!candidateOpensVersionSubmenu
|
|
709
|
+
) {
|
|
710
|
+
score += 900;
|
|
711
|
+
}
|
|
712
|
+
if (candidateIsNonProGpt55Thinking) {
|
|
713
|
+
score += scoreNonProGpt55ThinkingLabel(normalizedText);
|
|
714
|
+
}
|
|
715
|
+
if (candidateClearsProForThinking) {
|
|
716
|
+
score += scoreNonProGpt55ThinkingLabel(normalizedText) + 600;
|
|
717
|
+
}
|
|
718
|
+
if (
|
|
719
|
+
desiredVersion &&
|
|
720
|
+
candidateTextVersion === desiredVersion &&
|
|
721
|
+
!candidateOpensInstantSubmenu &&
|
|
722
|
+
!(wantsThinking && desiredVersion === '5-5' && normalizedText === 'gpt 5 5')
|
|
723
|
+
) {
|
|
724
|
+
score += 1200;
|
|
725
|
+
}
|
|
726
|
+
if (candidateOpensInstantSubmenu) {
|
|
727
|
+
score += 300;
|
|
728
|
+
}
|
|
729
|
+
if (candidateOpensVersionSubmenu) {
|
|
730
|
+
score += 500;
|
|
731
|
+
}
|
|
732
|
+
if (candidateIsGpt55ThinkingFamily) {
|
|
733
|
+
score += 260;
|
|
734
|
+
}
|
|
485
735
|
if (normalizedText && normalizedTarget) {
|
|
486
736
|
if (normalizedText === normalizedTarget) {
|
|
487
737
|
score += 500;
|
|
@@ -517,7 +767,14 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
517
767
|
}
|
|
518
768
|
// Similarly for Thinking variant
|
|
519
769
|
if (wantsThinking) {
|
|
520
|
-
if (
|
|
770
|
+
if (
|
|
771
|
+
!candidateIsGpt55ThinkingFamily &&
|
|
772
|
+
!candidateIsNonProGpt55Thinking &&
|
|
773
|
+
!candidateClearsProForThinking &&
|
|
774
|
+
!candidateOpensVersionSubmenu &&
|
|
775
|
+
!normalizedText.includes('thinking') &&
|
|
776
|
+
!normalizedTestId.includes('thinking')
|
|
777
|
+
) {
|
|
521
778
|
score -= 80;
|
|
522
779
|
}
|
|
523
780
|
} else if (normalizedText.includes('thinking') || normalizedTestId.includes('thinking')) {
|
|
@@ -558,6 +815,24 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
558
815
|
);
|
|
559
816
|
return pickerMenus.concat(textFallbackMenus);
|
|
560
817
|
};
|
|
818
|
+
const isSubmenuOption = (node, testid) =>
|
|
819
|
+
(testid ?? '').toLowerCase().includes('submenu') ||
|
|
820
|
+
(testid ?? '').toLowerCase() === 'model-configure-modal' ||
|
|
821
|
+
(node?.getAttribute?.('role') === 'combobox' &&
|
|
822
|
+
node?.getAttribute?.('aria-labelledby') === 'model-selection-label') ||
|
|
823
|
+
node?.getAttribute?.('aria-haspopup') === 'menu' ||
|
|
824
|
+
node?.getAttribute?.('data-has-submenu') !== null;
|
|
825
|
+
const canTrustSelectedOption = (node, normalizedText, testid) => {
|
|
826
|
+
if (!optionIsSelected(node)) return false;
|
|
827
|
+
if (getConfigurationDialog() && !configuredSelectionMatchesTarget()) return false;
|
|
828
|
+
const optionVersion = versionFromLabel(normalizedText) ?? versionFromTestId(testid);
|
|
829
|
+
if (desiredVersion && optionVersion !== desiredVersion) return false;
|
|
830
|
+
const currentButtonLabel = normalizeText(getButtonLabel());
|
|
831
|
+
return !labelHasProWord(currentButtonLabel) && !hasProComposerPill();
|
|
832
|
+
};
|
|
833
|
+
const openedSubmenuKeys = new Set();
|
|
834
|
+
const submenuKey = (normalizedText, testid) =>
|
|
835
|
+
normalizeText(testid ?? '') + '|' + normalizedText;
|
|
561
836
|
|
|
562
837
|
const findBestOption = () => {
|
|
563
838
|
// Walk through every menu item and keep whichever earns the highest score.
|
|
@@ -566,19 +841,33 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
566
841
|
for (const menu of menus) {
|
|
567
842
|
const buttons = Array.from(menu.querySelectorAll(${menuItemLiteral}));
|
|
568
843
|
for (const option of buttons) {
|
|
569
|
-
if (
|
|
844
|
+
if (isNestedEffortControl(option, menu)) {
|
|
570
845
|
continue;
|
|
571
846
|
}
|
|
572
847
|
const text = option.textContent ?? '';
|
|
573
848
|
const normalizedText = normalizeText(text);
|
|
574
849
|
const testid = option.getAttribute('data-testid') ?? '';
|
|
575
|
-
const
|
|
850
|
+
const optionSubmenuKey = submenuKey(normalizedText, testid);
|
|
851
|
+
if (isSubmenuOption(option, testid) && openedSubmenuKeys.has(optionSubmenuKey)) {
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
let score = scoreOption(normalizedText, testid, option);
|
|
576
855
|
if (score <= 0) {
|
|
577
856
|
continue;
|
|
578
857
|
}
|
|
858
|
+
if (canTrustSelectedOption(option, normalizedText, testid)) {
|
|
859
|
+
score += 1000;
|
|
860
|
+
}
|
|
579
861
|
const label = getOptionLabel(option);
|
|
580
862
|
if (!bestMatch || score > bestMatch.score) {
|
|
581
|
-
bestMatch = {
|
|
863
|
+
bestMatch = {
|
|
864
|
+
node: option,
|
|
865
|
+
label,
|
|
866
|
+
score,
|
|
867
|
+
testid,
|
|
868
|
+
normalizedText,
|
|
869
|
+
submenuKey: optionSubmenuKey,
|
|
870
|
+
};
|
|
582
871
|
}
|
|
583
872
|
}
|
|
584
873
|
}
|
|
@@ -613,6 +902,28 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
613
902
|
};
|
|
614
903
|
check();
|
|
615
904
|
});
|
|
905
|
+
const dispatchHoverSequence = (target) => {
|
|
906
|
+
if (!target || !(target instanceof EventTarget)) return false;
|
|
907
|
+
const types = ['pointerover', 'pointerenter', 'mouseover', 'mouseenter', 'pointermove', 'mousemove'];
|
|
908
|
+
for (const type of types) {
|
|
909
|
+
try {
|
|
910
|
+
const common = { bubbles: true, cancelable: true, view: window };
|
|
911
|
+
const event =
|
|
912
|
+
type.startsWith('pointer') && 'PointerEvent' in window
|
|
913
|
+
? new PointerEvent(type, { ...common, pointerId: 1, pointerType: 'mouse' })
|
|
914
|
+
: new MouseEvent(type, common);
|
|
915
|
+
target.dispatchEvent(event);
|
|
916
|
+
} catch {}
|
|
917
|
+
}
|
|
918
|
+
try {
|
|
919
|
+
target.focus?.();
|
|
920
|
+
} catch {}
|
|
921
|
+
return true;
|
|
922
|
+
};
|
|
923
|
+
const openSubmenuOption = (node) => {
|
|
924
|
+
dispatchHoverSequence(node);
|
|
925
|
+
dispatchClickSequence(node);
|
|
926
|
+
};
|
|
616
927
|
|
|
617
928
|
return new Promise((resolve) => {
|
|
618
929
|
const start = performance.now();
|
|
@@ -648,6 +959,13 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
648
959
|
const openDelay = () => new Promise((r) => setTimeout(r, INITIAL_WAIT_MS));
|
|
649
960
|
let initialized = false;
|
|
650
961
|
const attempt = async () => {
|
|
962
|
+
if (performance.now() - start > MAX_WAIT_MS) {
|
|
963
|
+
resolve({
|
|
964
|
+
status: 'option-not-found',
|
|
965
|
+
hint: { temporaryChat: detectTemporaryChat(), availableOptions: collectAvailableOptions() },
|
|
966
|
+
});
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
651
969
|
if (!initialized) {
|
|
652
970
|
initialized = true;
|
|
653
971
|
await openDelay();
|
|
@@ -655,26 +973,33 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
655
973
|
ensureMenuOpen();
|
|
656
974
|
const match = findBestOption();
|
|
657
975
|
if (match) {
|
|
658
|
-
if (
|
|
976
|
+
if (
|
|
977
|
+
activeSelectionMatchesTarget() ||
|
|
978
|
+
canTrustSelectedOption(match.node, match.normalizedText, match.testid)
|
|
979
|
+
) {
|
|
980
|
+
const resolvedLabel = getResolvedLabel(match.label);
|
|
659
981
|
closeMenu();
|
|
660
|
-
resolve({ status: 'already-selected', label:
|
|
982
|
+
resolve({ status: 'already-selected', label: resolvedLabel });
|
|
661
983
|
return;
|
|
662
984
|
}
|
|
663
985
|
const previousButtonLabel = normalizeText(getButtonLabel());
|
|
664
986
|
const previousComposerSignal = readComposerModelSignal();
|
|
665
|
-
dispatchClickSequence(match.node);
|
|
666
987
|
// Submenus (e.g. "Legacy models") need a second pass to pick the actual model option.
|
|
667
988
|
// Keep scanning once the submenu opens instead of treating the submenu click as a final switch.
|
|
668
|
-
const isSubmenu = (match.
|
|
989
|
+
const isSubmenu = isSubmenuOption(match.node, match.testid);
|
|
669
990
|
if (isSubmenu) {
|
|
991
|
+
openedSubmenuKeys.add(match.submenuKey);
|
|
992
|
+
openSubmenuOption(match.node);
|
|
670
993
|
setTimeout(attempt, REOPEN_INTERVAL_MS / 2);
|
|
671
994
|
return;
|
|
672
995
|
}
|
|
996
|
+
dispatchClickSequence(match.node);
|
|
673
997
|
// Wait for the selected model signal to settle before reopening the picker.
|
|
674
998
|
waitForTargetSelection(previousButtonLabel, previousComposerSignal).then((selectionSettled) => {
|
|
675
999
|
if (selectionSettled === 'target') {
|
|
1000
|
+
const resolvedLabel = getResolvedLabel(match.label);
|
|
676
1001
|
closeMenu();
|
|
677
|
-
resolve({ status: 'switched', label:
|
|
1002
|
+
resolve({ status: 'switched', label: resolvedLabel });
|
|
678
1003
|
return;
|
|
679
1004
|
}
|
|
680
1005
|
attempt();
|
|
@@ -783,6 +1108,34 @@ function buildModelMatchersLiteral(targetModel) {
|
|
|
783
1108
|
testIdTokens.add("gpt5-4");
|
|
784
1109
|
testIdTokens.add("gpt54");
|
|
785
1110
|
}
|
|
1111
|
+
// Numeric variations (5.3 ↔ 53 ↔ gpt-5-3)
|
|
1112
|
+
if (base.includes("5.3") || base.includes("5-3") || base.includes("53")) {
|
|
1113
|
+
push("5.3", labelTokens);
|
|
1114
|
+
push("gpt-5.3", labelTokens);
|
|
1115
|
+
push("gpt5.3", labelTokens);
|
|
1116
|
+
push("gpt-5-3", labelTokens);
|
|
1117
|
+
push("gpt5-3", labelTokens);
|
|
1118
|
+
push("gpt53", labelTokens);
|
|
1119
|
+
push("chatgpt 5.3", labelTokens);
|
|
1120
|
+
if (base.includes("thinking")) {
|
|
1121
|
+
push("thinking", labelTokens);
|
|
1122
|
+
testIdTokens.add("model-switcher-gpt-5-3-thinking");
|
|
1123
|
+
testIdTokens.add("gpt-5-3-thinking");
|
|
1124
|
+
testIdTokens.add("gpt-5.3-thinking");
|
|
1125
|
+
}
|
|
1126
|
+
if (base.includes("instant")) {
|
|
1127
|
+
push("instant", labelTokens);
|
|
1128
|
+
testIdTokens.add("model-switcher-gpt-5-3-instant");
|
|
1129
|
+
testIdTokens.add("gpt-5-3-instant");
|
|
1130
|
+
testIdTokens.add("gpt-5.3-instant");
|
|
1131
|
+
}
|
|
1132
|
+
if (!base.includes("pro") && !base.includes("thinking") && !base.includes("instant")) {
|
|
1133
|
+
testIdTokens.add("model-switcher-gpt-5-3");
|
|
1134
|
+
}
|
|
1135
|
+
testIdTokens.add("gpt-5-3");
|
|
1136
|
+
testIdTokens.add("gpt5-3");
|
|
1137
|
+
testIdTokens.add("gpt53");
|
|
1138
|
+
}
|
|
786
1139
|
// Numeric variations (5.1 ↔ 51 ↔ gpt-5-1)
|
|
787
1140
|
if (base.includes("5.1") || base.includes("5-1") || base.includes("51")) {
|
|
788
1141
|
push("5.1", labelTokens);
|
|
@@ -857,6 +1210,11 @@ function buildModelMatchersLiteral(targetModel) {
|
|
|
857
1210
|
testIdTokens.add("gpt-5-4-pro");
|
|
858
1211
|
testIdTokens.add("gpt54pro");
|
|
859
1212
|
}
|
|
1213
|
+
if (base.includes("5.3") || base.includes("5-3") || base.includes("53")) {
|
|
1214
|
+
testIdTokens.add("gpt-5.3-pro");
|
|
1215
|
+
testIdTokens.add("gpt-5-3-pro");
|
|
1216
|
+
testIdTokens.add("gpt53pro");
|
|
1217
|
+
}
|
|
860
1218
|
if (base.includes("5.1") || base.includes("5-1") || base.includes("51")) {
|
|
861
1219
|
testIdTokens.add("gpt-5.1-pro");
|
|
862
1220
|
testIdTokens.add("gpt-5-1-pro");
|