@steipete/oracle 0.13.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 +22 -11
- package/dist/bin/oracle-cli.js +71 -71
- package/dist/src/browser/actions/assistantResponse.js +93 -10
- package/dist/src/browser/actions/deepResearch.js +180 -52
- package/dist/src/browser/actions/modelSelection.js +357 -40
- package/dist/src/browser/actions/navigation.js +294 -65
- package/dist/src/browser/actions/promptComposer.js +50 -4
- package/dist/src/browser/actions/thinkingTime.js +517 -105
- package/dist/src/browser/artifacts.js +19 -0
- package/dist/src/browser/chatgptFiles.js +797 -0
- package/dist/src/browser/chatgptImages.js +256 -27
- package/dist/src/browser/chromeLifecycle.js +4 -0
- package/dist/src/browser/config.js +2 -0
- package/dist/src/browser/index.js +404 -41
- package/dist/src/browser/pageActions.js +1 -1
- package/dist/src/browser/policies.js +2 -6
- package/dist/src/browser/projectSourcesRunner.js +16 -3
- package/dist/src/browser/prompt.js +161 -36
- package/dist/src/browser/reattach.js +6 -2
- package/dist/src/browser/reattachability.js +22 -10
- package/dist/src/browser/recoverConversation.js +73 -0
- package/dist/src/browser/sessionRunner.js +4 -1
- package/dist/src/cli/browserConfig.js +6 -2
- package/dist/src/cli/browserDefaults.js +2 -1
- package/dist/src/cli/browserTabs.js +129 -54
- package/dist/src/cli/followup.js +72 -0
- package/dist/src/cli/help.js +1 -1
- package/dist/src/cli/options.js +24 -0
- package/dist/src/cli/reattachGuidance.js +8 -0
- package/dist/src/cli/runOptions.js +2 -12
- package/dist/src/cli/sessionCommand.js +4 -0
- package/dist/src/cli/sessionDisplay.js +17 -3
- package/dist/src/cli/sessionLineage.js +7 -4
- package/dist/src/cli/sessionRunner.js +16 -1
- package/dist/src/gemini-web/client.js +5 -10
- package/dist/src/gemini-web/executor.js +1 -24
- package/dist/src/gemini-web/models.js +83 -0
- package/dist/src/mcp/server.js +2 -0
- package/dist/src/mcp/tools/chatgptImage.js +132 -0
- package/dist/src/mcp/tools/consult.js +270 -175
- package/dist/src/mcp/types.js +13 -2
- package/dist/src/mcp/utils.js +87 -1
- package/dist/src/oracle/config.js +27 -1
- package/dist/src/oracle/files.js +4 -6
- package/dist/src/oracle/geminiModels.js +2 -0
- package/dist/src/oracle/markdown.js +36 -3
- package/dist/src/oracle/modelResolver.js +21 -13
- package/dist/src/oracle/promptAssembly.js +2 -4
- package/dist/src/oracle/request.js +3 -5
- package/dist/src/oracle/thinkingTime.js +40 -0
- package/dist/src/oracle/tokenStats.js +5 -1
- package/dist/src/oracle.js +1 -1
- package/dist/src/sessionManager.js +1 -0
- package/dist/vendor/oracle-notifier/build-notifier.sh +0 -0
- package/package.json +47 -58
- package/vendor/oracle-notifier/build-notifier.sh +0 -0
- package/dist/bin/oracle.js +0 -569
- 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
|
@@ -12,13 +12,12 @@ 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
|
-
|
|
17
|
-
const label = result.label ?? desiredModel;
|
|
15
|
+
case "switched": {
|
|
16
|
+
const label = result.label?.trim() || (strategy === "current" ? null : desiredModel);
|
|
18
17
|
if (strategy !== "current") {
|
|
19
|
-
assertResolvedModelSelection(desiredModel, label);
|
|
18
|
+
assertResolvedModelSelection(desiredModel, label ?? desiredModel);
|
|
20
19
|
}
|
|
21
|
-
logger(`Model picker: ${label}`);
|
|
20
|
+
logger(`Model picker: ${label ?? "current model (label unavailable)"}`);
|
|
22
21
|
return {
|
|
23
22
|
requestedModel: desiredModel,
|
|
24
23
|
resolvedLabel: label,
|
|
@@ -41,7 +40,7 @@ export async function ensureModelSelection(Runtime, desiredModel, logger, strate
|
|
|
41
40
|
}
|
|
42
41
|
default: {
|
|
43
42
|
await logDomFailure(Runtime, logger, "model-switcher-button");
|
|
44
|
-
throw new Error("Unable to locate the ChatGPT model selector button.");
|
|
43
|
+
throw new Error("Unable to locate the ChatGPT model selector button. If the desired model is already selected in the browser, retry with --browser-model-strategy current; otherwise retry with --browser-model-strategy ignore to skip model selection.");
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
46
|
}
|
|
@@ -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 = () => {
|
|
@@ -205,12 +218,17 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
205
218
|
return Array.from(document.querySelectorAll('button.__composer-pill')).find(looksLikeModelPill) ?? null;
|
|
206
219
|
};
|
|
207
220
|
|
|
208
|
-
const button = findModelButton();
|
|
209
|
-
if (!button) {
|
|
210
|
-
return { status: 'button-missing' };
|
|
211
|
-
}
|
|
212
|
-
|
|
213
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
|
+
}
|
|
230
|
+
const button = findModelButton();
|
|
231
|
+
if (!button) return;
|
|
214
232
|
try {
|
|
215
233
|
if (dispatchClickSequence(button)) {
|
|
216
234
|
lastPointerClick = performance.now();
|
|
@@ -230,10 +248,29 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
230
248
|
} catch {}
|
|
231
249
|
};
|
|
232
250
|
|
|
233
|
-
const getButtonLabel = () => (
|
|
251
|
+
const getButtonLabel = () => (findModelButton()?.textContent ?? '').trim();
|
|
234
252
|
const getComposerModelLabel = () =>
|
|
235
253
|
(document.querySelector(COMPOSER_MODEL_SIGNAL_SELECTOR)?.textContent ?? '').trim();
|
|
236
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
|
+
};
|
|
237
274
|
const withProPillSignal = (label) => {
|
|
238
275
|
const resolved = label || '';
|
|
239
276
|
if (!wantsPro || !hasProComposerPill()) return resolved;
|
|
@@ -243,27 +280,109 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
243
280
|
if (normalized.includes('pro')) return resolved;
|
|
244
281
|
return resolved + ' + Pro';
|
|
245
282
|
};
|
|
246
|
-
const getResolvedLabel = (fallback) =>
|
|
247
|
-
withProPillSignal(getComposerModelLabel() || getButtonLabel() || fallback);
|
|
248
283
|
const isThinkingEffortLabel = (label) =>
|
|
249
|
-
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
|
+
};
|
|
250
361
|
if (MODEL_STRATEGY === 'current') {
|
|
251
|
-
const currentLabel = getResolvedLabel(
|
|
362
|
+
const currentLabel = getResolvedLabel('') || null;
|
|
252
363
|
return {
|
|
253
364
|
status: 'already-selected',
|
|
254
365
|
label: currentLabel,
|
|
255
366
|
};
|
|
256
367
|
}
|
|
368
|
+
|
|
369
|
+
const button = findModelButton();
|
|
370
|
+
if (!button) {
|
|
371
|
+
return { status: 'button-missing' };
|
|
372
|
+
}
|
|
257
373
|
const buttonMatchesTarget = () => {
|
|
374
|
+
if (configuredSelectionMatchesTarget()) return true;
|
|
258
375
|
const normalizedLabel = normalizeText(getButtonLabel());
|
|
259
376
|
if (!normalizedLabel) return false;
|
|
377
|
+
if (wantsThinking && !wantsPro && hasProComposerPill()) return false;
|
|
260
378
|
if (isTargetGpt55VisibleAlias(normalizedLabel)) return true;
|
|
261
379
|
if (
|
|
262
380
|
wantsThinking &&
|
|
263
381
|
desiredVersion === '5-5' &&
|
|
264
382
|
!hasProComposerPill() &&
|
|
265
383
|
isThinkingEffortLabel(normalizedLabel) &&
|
|
266
|
-
|
|
384
|
+
(isNonProIntelligenceThinkingLabel(normalizedLabel) ||
|
|
385
|
+
isTargetGpt55VisibleAlias(readComposerModelSignal()))
|
|
267
386
|
) {
|
|
268
387
|
return true;
|
|
269
388
|
}
|
|
@@ -281,6 +400,7 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
281
400
|
if (desiredVersion) {
|
|
282
401
|
if (desiredVersion === '5-5' && !normalizedLabel.includes('5 5')) return false;
|
|
283
402
|
if (desiredVersion === '5-4' && !normalizedLabel.includes('5 4')) return false;
|
|
403
|
+
if (desiredVersion === '5-3' && !normalizedLabel.includes('5 3')) return false;
|
|
284
404
|
if (desiredVersion === '5-2' && !normalizedLabel.includes('5 2')) return false;
|
|
285
405
|
if (desiredVersion === '5-1' && !normalizedLabel.includes('5 1')) return false;
|
|
286
406
|
if (desiredVersion === '5-0' && !normalizedLabel.includes('5 0')) return false;
|
|
@@ -290,9 +410,9 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
290
410
|
if (wantsInstant && !normalizedLabel.includes('instant')) return false;
|
|
291
411
|
if (
|
|
292
412
|
wantsThinking &&
|
|
293
|
-
desiredVersion
|
|
294
|
-
|
|
295
|
-
!normalizedLabel
|
|
413
|
+
desiredVersion &&
|
|
414
|
+
versionFromLabel(normalizedLabel) === desiredVersion &&
|
|
415
|
+
!labelHasProWord(normalizedLabel)
|
|
296
416
|
) {
|
|
297
417
|
return true;
|
|
298
418
|
}
|
|
@@ -357,10 +477,21 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
357
477
|
};
|
|
358
478
|
|
|
359
479
|
const getOptionLabel = (node) => node?.textContent?.trim() ?? '';
|
|
360
|
-
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) =>
|
|
361
489
|
node instanceof HTMLElement &&
|
|
362
490
|
(node.getAttribute('data-model-picker-thinking-effort-action') === 'true' ||
|
|
363
|
-
|
|
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));
|
|
364
495
|
const optionIsSelected = (node) => {
|
|
365
496
|
if (!(node instanceof HTMLElement)) {
|
|
366
497
|
return false;
|
|
@@ -380,13 +511,28 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
380
511
|
return false;
|
|
381
512
|
};
|
|
382
513
|
|
|
383
|
-
const scoreOption = (normalizedText, testid) => {
|
|
514
|
+
const scoreOption = (normalizedText, testid, node) => {
|
|
384
515
|
// Assign a score to every node so we can pick the most likely match without brittle equality checks.
|
|
385
516
|
if (!normalizedText && !testid) {
|
|
386
517
|
return 0;
|
|
387
518
|
}
|
|
388
519
|
let score = 0;
|
|
389
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
|
+
}
|
|
390
536
|
let exactTestIdMatch = false;
|
|
391
537
|
if (normalizedTestId) {
|
|
392
538
|
if (desiredVersion) {
|
|
@@ -409,6 +555,12 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
409
555
|
normalizedTestId.includes('gpt-5-4') ||
|
|
410
556
|
normalizedTestId.includes('gpt-5.4') ||
|
|
411
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');
|
|
412
564
|
const has51 =
|
|
413
565
|
normalizedTestId.includes('5-1') ||
|
|
414
566
|
normalizedTestId.includes('5.1') ||
|
|
@@ -421,7 +573,19 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
421
573
|
normalizedTestId.includes('gpt-5-0') ||
|
|
422
574
|
normalizedTestId.includes('gpt-5.0') ||
|
|
423
575
|
normalizedTestId.includes('gpt50');
|
|
424
|
-
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;
|
|
425
589
|
// If a candidate advertises a different version, ignore it entirely.
|
|
426
590
|
if (candidateVersion && candidateVersion !== desiredVersion) {
|
|
427
591
|
return 0;
|
|
@@ -450,10 +614,44 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
450
614
|
}
|
|
451
615
|
}
|
|
452
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'));
|
|
453
647
|
const candidateHasThinking =
|
|
454
648
|
normalizedText.includes('thinking') ||
|
|
455
649
|
normalizedTestId.includes('thinking') ||
|
|
456
|
-
|
|
650
|
+
candidateIsNonProGpt55Thinking ||
|
|
651
|
+
candidateClearsProForThinking ||
|
|
652
|
+
candidateOpensVersionSubmenu ||
|
|
653
|
+
candidateIsGpt55ThinkingFamily ||
|
|
654
|
+
(wantsThinking && desiredVersion && (exactTestIdMatch || candidateSelectsDesiredVersion));
|
|
457
655
|
const candidateHasLegacyProVersion = labelHasLegacyProVersion(normalizedText);
|
|
458
656
|
const candidateHasPro =
|
|
459
657
|
labelHasProWord(normalizedText) ||
|
|
@@ -462,11 +660,11 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
462
660
|
const candidateHasInstant =
|
|
463
661
|
normalizedText.includes('instant') || normalizedTestId.includes('instant');
|
|
464
662
|
if (wantsPro && candidateHasThinking) return 0;
|
|
465
|
-
if (wantsPro && candidateHasLegacyProVersion) return 0;
|
|
466
|
-
if (wantsPro && !candidateHasPro) return 0;
|
|
467
|
-
if (wantsInstant && !candidateHasInstant) return 0;
|
|
663
|
+
if (wantsPro && candidateHasLegacyProVersion && !candidateSelectsDesiredVersion) return 0;
|
|
664
|
+
if (wantsPro && !candidateHasPro && !candidateSelectsDesiredVersion) return 0;
|
|
665
|
+
if (wantsInstant && !candidateHasInstant && !candidateSelectsDesiredVersion) return 0;
|
|
468
666
|
if (wantsThinking && candidateHasPro) return 0;
|
|
469
|
-
if (wantsThinking && !candidateHasThinking) return 0;
|
|
667
|
+
if (wantsThinking && !candidateHasThinking && !candidateSelectsDesiredVersion) return 0;
|
|
470
668
|
if (desiredVersion === '5-5' && normalizedText && !candidateGpt55VisibleAlias) {
|
|
471
669
|
const candidateHasVersion =
|
|
472
670
|
normalizedText.includes('5 5') ||
|
|
@@ -480,6 +678,39 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
480
678
|
if (candidateGpt55VisibleAlias) {
|
|
481
679
|
score += 900;
|
|
482
680
|
}
|
|
681
|
+
if (wantsPro && candidateHasPro && !candidateSelectsDesiredVersion) {
|
|
682
|
+
score += 900;
|
|
683
|
+
}
|
|
684
|
+
if (wantsInstant && candidateHasInstant && !candidateSelectsDesiredVersion) {
|
|
685
|
+
score += 900;
|
|
686
|
+
}
|
|
687
|
+
if (
|
|
688
|
+
wantsThinking &&
|
|
689
|
+
candidateHasThinking &&
|
|
690
|
+
!candidateSelectsDesiredVersion &&
|
|
691
|
+
!candidateOpensVersionSubmenu
|
|
692
|
+
) {
|
|
693
|
+
score += 900;
|
|
694
|
+
}
|
|
695
|
+
if (candidateIsNonProGpt55Thinking) {
|
|
696
|
+
score += scoreNonProGpt55ThinkingLabel(normalizedText);
|
|
697
|
+
}
|
|
698
|
+
if (candidateClearsProForThinking) {
|
|
699
|
+
score += scoreNonProGpt55ThinkingLabel(normalizedText) + 600;
|
|
700
|
+
}
|
|
701
|
+
if (
|
|
702
|
+
desiredVersion &&
|
|
703
|
+
candidateTextVersion === desiredVersion &&
|
|
704
|
+
!(wantsThinking && desiredVersion === '5-5' && normalizedText === 'gpt 5 5')
|
|
705
|
+
) {
|
|
706
|
+
score += 1200;
|
|
707
|
+
}
|
|
708
|
+
if (candidateOpensVersionSubmenu) {
|
|
709
|
+
score += 500;
|
|
710
|
+
}
|
|
711
|
+
if (candidateIsGpt55ThinkingFamily) {
|
|
712
|
+
score += 260;
|
|
713
|
+
}
|
|
483
714
|
if (normalizedText && normalizedTarget) {
|
|
484
715
|
if (normalizedText === normalizedTarget) {
|
|
485
716
|
score += 500;
|
|
@@ -515,7 +746,14 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
515
746
|
}
|
|
516
747
|
// Similarly for Thinking variant
|
|
517
748
|
if (wantsThinking) {
|
|
518
|
-
if (
|
|
749
|
+
if (
|
|
750
|
+
!candidateIsGpt55ThinkingFamily &&
|
|
751
|
+
!candidateIsNonProGpt55Thinking &&
|
|
752
|
+
!candidateClearsProForThinking &&
|
|
753
|
+
!candidateOpensVersionSubmenu &&
|
|
754
|
+
!normalizedText.includes('thinking') &&
|
|
755
|
+
!normalizedTestId.includes('thinking')
|
|
756
|
+
) {
|
|
519
757
|
score -= 80;
|
|
520
758
|
}
|
|
521
759
|
} else if (normalizedText.includes('thinking') || normalizedTestId.includes('thinking')) {
|
|
@@ -556,6 +794,21 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
556
794
|
);
|
|
557
795
|
return pickerMenus.concat(textFallbackMenus);
|
|
558
796
|
};
|
|
797
|
+
const isSubmenuOption = (node, testid) =>
|
|
798
|
+
(testid ?? '').toLowerCase().includes('submenu') ||
|
|
799
|
+
(testid ?? '').toLowerCase() === 'model-configure-modal' ||
|
|
800
|
+
(node?.getAttribute?.('role') === 'combobox' &&
|
|
801
|
+
node?.getAttribute?.('aria-labelledby') === 'model-selection-label') ||
|
|
802
|
+
node?.getAttribute?.('aria-haspopup') === 'menu' ||
|
|
803
|
+
node?.getAttribute?.('data-has-submenu') !== null;
|
|
804
|
+
const canTrustSelectedOption = (node, normalizedText, testid) => {
|
|
805
|
+
if (!optionIsSelected(node)) return false;
|
|
806
|
+
if (getConfigurationDialog() && !configuredSelectionMatchesTarget()) return false;
|
|
807
|
+
const optionVersion = versionFromLabel(normalizedText) ?? versionFromTestId(testid);
|
|
808
|
+
if (desiredVersion && optionVersion !== desiredVersion) return false;
|
|
809
|
+
const currentButtonLabel = normalizeText(getButtonLabel());
|
|
810
|
+
return !labelHasProWord(currentButtonLabel) && !hasProComposerPill();
|
|
811
|
+
};
|
|
559
812
|
|
|
560
813
|
const findBestOption = () => {
|
|
561
814
|
// Walk through every menu item and keep whichever earns the highest score.
|
|
@@ -564,16 +817,19 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
564
817
|
for (const menu of menus) {
|
|
565
818
|
const buttons = Array.from(menu.querySelectorAll(${menuItemLiteral}));
|
|
566
819
|
for (const option of buttons) {
|
|
567
|
-
if (
|
|
820
|
+
if (isNestedEffortControl(option, menu)) {
|
|
568
821
|
continue;
|
|
569
822
|
}
|
|
570
823
|
const text = option.textContent ?? '';
|
|
571
824
|
const normalizedText = normalizeText(text);
|
|
572
825
|
const testid = option.getAttribute('data-testid') ?? '';
|
|
573
|
-
|
|
826
|
+
let score = scoreOption(normalizedText, testid, option);
|
|
574
827
|
if (score <= 0) {
|
|
575
828
|
continue;
|
|
576
829
|
}
|
|
830
|
+
if (canTrustSelectedOption(option, normalizedText, testid)) {
|
|
831
|
+
score += 1000;
|
|
832
|
+
}
|
|
577
833
|
const label = getOptionLabel(option);
|
|
578
834
|
if (!bestMatch || score > bestMatch.score) {
|
|
579
835
|
bestMatch = { node: option, label, score, testid, normalizedText };
|
|
@@ -611,6 +867,28 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
611
867
|
};
|
|
612
868
|
check();
|
|
613
869
|
});
|
|
870
|
+
const dispatchHoverSequence = (target) => {
|
|
871
|
+
if (!target || !(target instanceof EventTarget)) return false;
|
|
872
|
+
const types = ['pointerover', 'pointerenter', 'mouseover', 'mouseenter', 'pointermove', 'mousemove'];
|
|
873
|
+
for (const type of types) {
|
|
874
|
+
try {
|
|
875
|
+
const common = { bubbles: true, cancelable: true, view: window };
|
|
876
|
+
const event =
|
|
877
|
+
type.startsWith('pointer') && 'PointerEvent' in window
|
|
878
|
+
? new PointerEvent(type, { ...common, pointerId: 1, pointerType: 'mouse' })
|
|
879
|
+
: new MouseEvent(type, common);
|
|
880
|
+
target.dispatchEvent(event);
|
|
881
|
+
} catch {}
|
|
882
|
+
}
|
|
883
|
+
try {
|
|
884
|
+
target.focus?.();
|
|
885
|
+
} catch {}
|
|
886
|
+
return true;
|
|
887
|
+
};
|
|
888
|
+
const openSubmenuOption = (node) => {
|
|
889
|
+
dispatchHoverSequence(node);
|
|
890
|
+
dispatchClickSequence(node);
|
|
891
|
+
};
|
|
614
892
|
|
|
615
893
|
return new Promise((resolve) => {
|
|
616
894
|
const start = performance.now();
|
|
@@ -653,26 +931,32 @@ function buildModelSelectionExpression(targetModel, strategy) {
|
|
|
653
931
|
ensureMenuOpen();
|
|
654
932
|
const match = findBestOption();
|
|
655
933
|
if (match) {
|
|
656
|
-
if (
|
|
934
|
+
if (
|
|
935
|
+
activeSelectionMatchesTarget() ||
|
|
936
|
+
canTrustSelectedOption(match.node, match.normalizedText, match.testid)
|
|
937
|
+
) {
|
|
938
|
+
const resolvedLabel = getResolvedLabel(match.label);
|
|
657
939
|
closeMenu();
|
|
658
|
-
resolve({ status: 'already-selected', label:
|
|
940
|
+
resolve({ status: 'already-selected', label: resolvedLabel });
|
|
659
941
|
return;
|
|
660
942
|
}
|
|
661
943
|
const previousButtonLabel = normalizeText(getButtonLabel());
|
|
662
944
|
const previousComposerSignal = readComposerModelSignal();
|
|
663
|
-
dispatchClickSequence(match.node);
|
|
664
945
|
// Submenus (e.g. "Legacy models") need a second pass to pick the actual model option.
|
|
665
946
|
// Keep scanning once the submenu opens instead of treating the submenu click as a final switch.
|
|
666
|
-
const isSubmenu = (match.
|
|
947
|
+
const isSubmenu = isSubmenuOption(match.node, match.testid);
|
|
667
948
|
if (isSubmenu) {
|
|
949
|
+
openSubmenuOption(match.node);
|
|
668
950
|
setTimeout(attempt, REOPEN_INTERVAL_MS / 2);
|
|
669
951
|
return;
|
|
670
952
|
}
|
|
953
|
+
dispatchClickSequence(match.node);
|
|
671
954
|
// Wait for the selected model signal to settle before reopening the picker.
|
|
672
955
|
waitForTargetSelection(previousButtonLabel, previousComposerSignal).then((selectionSettled) => {
|
|
673
956
|
if (selectionSettled === 'target') {
|
|
957
|
+
const resolvedLabel = getResolvedLabel(match.label);
|
|
674
958
|
closeMenu();
|
|
675
|
-
resolve({ status: 'switched', label:
|
|
959
|
+
resolve({ status: 'switched', label: resolvedLabel });
|
|
676
960
|
return;
|
|
677
961
|
}
|
|
678
962
|
attempt();
|
|
@@ -781,6 +1065,34 @@ function buildModelMatchersLiteral(targetModel) {
|
|
|
781
1065
|
testIdTokens.add("gpt5-4");
|
|
782
1066
|
testIdTokens.add("gpt54");
|
|
783
1067
|
}
|
|
1068
|
+
// Numeric variations (5.3 ↔ 53 ↔ gpt-5-3)
|
|
1069
|
+
if (base.includes("5.3") || base.includes("5-3") || base.includes("53")) {
|
|
1070
|
+
push("5.3", labelTokens);
|
|
1071
|
+
push("gpt-5.3", labelTokens);
|
|
1072
|
+
push("gpt5.3", labelTokens);
|
|
1073
|
+
push("gpt-5-3", labelTokens);
|
|
1074
|
+
push("gpt5-3", labelTokens);
|
|
1075
|
+
push("gpt53", labelTokens);
|
|
1076
|
+
push("chatgpt 5.3", labelTokens);
|
|
1077
|
+
if (base.includes("thinking")) {
|
|
1078
|
+
push("thinking", labelTokens);
|
|
1079
|
+
testIdTokens.add("model-switcher-gpt-5-3-thinking");
|
|
1080
|
+
testIdTokens.add("gpt-5-3-thinking");
|
|
1081
|
+
testIdTokens.add("gpt-5.3-thinking");
|
|
1082
|
+
}
|
|
1083
|
+
if (base.includes("instant")) {
|
|
1084
|
+
push("instant", labelTokens);
|
|
1085
|
+
testIdTokens.add("model-switcher-gpt-5-3-instant");
|
|
1086
|
+
testIdTokens.add("gpt-5-3-instant");
|
|
1087
|
+
testIdTokens.add("gpt-5.3-instant");
|
|
1088
|
+
}
|
|
1089
|
+
if (!base.includes("pro") && !base.includes("thinking") && !base.includes("instant")) {
|
|
1090
|
+
testIdTokens.add("model-switcher-gpt-5-3");
|
|
1091
|
+
}
|
|
1092
|
+
testIdTokens.add("gpt-5-3");
|
|
1093
|
+
testIdTokens.add("gpt5-3");
|
|
1094
|
+
testIdTokens.add("gpt53");
|
|
1095
|
+
}
|
|
784
1096
|
// Numeric variations (5.1 ↔ 51 ↔ gpt-5-1)
|
|
785
1097
|
if (base.includes("5.1") || base.includes("5-1") || base.includes("51")) {
|
|
786
1098
|
push("5.1", labelTokens);
|
|
@@ -855,6 +1167,11 @@ function buildModelMatchersLiteral(targetModel) {
|
|
|
855
1167
|
testIdTokens.add("gpt-5-4-pro");
|
|
856
1168
|
testIdTokens.add("gpt54pro");
|
|
857
1169
|
}
|
|
1170
|
+
if (base.includes("5.3") || base.includes("5-3") || base.includes("53")) {
|
|
1171
|
+
testIdTokens.add("gpt-5.3-pro");
|
|
1172
|
+
testIdTokens.add("gpt-5-3-pro");
|
|
1173
|
+
testIdTokens.add("gpt53pro");
|
|
1174
|
+
}
|
|
858
1175
|
if (base.includes("5.1") || base.includes("5-1") || base.includes("51")) {
|
|
859
1176
|
testIdTokens.add("gpt-5.1-pro");
|
|
860
1177
|
testIdTokens.add("gpt-5-1-pro");
|