@steipete/oracle 0.19.0 → 0.20.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/dist/bin/oracle-cli.js +72 -29
- package/dist/src/browser/actions/deepResearch.js +168 -44
- package/dist/src/browser/actions/modelSelection.js +78 -1
- package/dist/src/browser/actions/promptComposer.js +3 -0
- package/dist/src/browser/actions/thinkingTime.js +49 -6
- package/dist/src/browser/actions/webSearch.js +96 -0
- package/dist/src/browser/chromeLifecycle.js +28 -16
- package/dist/src/browser/config.js +1 -1
- package/dist/src/browser/index.js +144 -49
- package/dist/src/browser/liveTabs.js +11 -4
- package/dist/src/browser/profileState.js +6 -1
- package/dist/src/browser/promptFingerprint.js +54 -0
- package/dist/src/browser/providers/chatgptDomProvider.js +1 -0
- package/dist/src/browser/reattach.js +178 -109
- package/dist/src/browser/recoveryTarget.js +156 -0
- package/dist/src/browser/sessionRunner.js +33 -8
- package/dist/src/browser/tabLeaseRegistry.js +20 -0
- package/dist/src/browser/targetClaim.js +54 -0
- package/dist/src/cli/browserConfig.js +33 -3
- package/dist/src/cli/browserDefaults.js +3 -0
- package/dist/src/cli/browserTabs.js +38 -3
- package/dist/src/cli/detach.js +10 -1
- package/dist/src/cli/detachedSession.js +36 -0
- package/dist/src/cli/options.js +15 -0
- package/dist/src/cli/recoveredBrowserHarvest.js +50 -0
- package/dist/src/cli/runOptions.js +19 -4
- package/dist/src/cli/sessionDisplay.js +15 -8
- package/dist/src/cli/sessionRunner.js +153 -41
- package/dist/src/mcp/tools/consult.js +2 -2
- package/dist/src/mcp/types.js +1 -1
- package/dist/src/oracle/config.js +14 -0
- package/dist/src/oracle/geminiModels.js +1 -0
- package/dist/src/oracle/run.js +27 -4
- package/dist/src/remote/client.js +3 -0
- package/dist/src/remote/server.js +2 -0
- package/dist/src/sessionManager.js +8 -1
- 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 +10 -10
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
|
@@ -9,7 +9,6 @@ import { formatResponseMetadata, formatTransportMetadata } from "./sessionDispla
|
|
|
9
9
|
import { markErrorLogged } from "./errorUtils.js";
|
|
10
10
|
import { sendSessionNotification, deriveNotificationSettingsFromMetadata, } from "./notifier.js";
|
|
11
11
|
import { sessionStore } from "../sessionStore.js";
|
|
12
|
-
import { wait } from "../sessionManager.js";
|
|
13
12
|
import { runMultiModelApiSession } from "../oracle/multiModelRunner.js";
|
|
14
13
|
import { MODEL_CONFIGS, DEFAULT_SYSTEM_PROMPT } from "../oracle/config.js";
|
|
15
14
|
import { isKnownModel } from "../oracle/modelResolver.js";
|
|
@@ -22,30 +21,71 @@ import { sanitizeOscProgress } from "./oscUtils.js";
|
|
|
22
21
|
import { readFiles } from "../oracle/files.js";
|
|
23
22
|
import { cwd as getCwd } from "node:process";
|
|
24
23
|
import { resumeBrowserSession } from "../browser/reattach.js";
|
|
24
|
+
import { recoveryCaptureFromRuntime, retireCancelledBrowserTarget, retireRecoveredBrowserTarget, } from "../browser/recoveryTarget.js";
|
|
25
25
|
import { hasRecoverableChatGptConversation } from "../browser/reattachability.js";
|
|
26
|
-
import { estimateTokenCount } from "../browser/utils.js";
|
|
26
|
+
import { delay, estimateTokenCount } from "../browser/utils.js";
|
|
27
27
|
import { computeFileSha256, sanitizeArtifactFilename } from "../browser/artifacts.js";
|
|
28
28
|
import { formatElapsed } from "../oracle/format.js";
|
|
29
29
|
import { formatBrowserReattachGuidance } from "./reattachGuidance.js";
|
|
30
|
+
import { BrowserRunCancelledError } from "../oracle/errors.js";
|
|
30
31
|
const isTty = process.stdout.isTTY;
|
|
31
32
|
const dim = (text) => (isTty ? kleur.dim(text) : text);
|
|
32
|
-
export async function performSessionRun({ sessionMeta, runOptions, mode, browserConfig, cwd, log, write, version, notifications, browserDeps, muteStdout = false, }) {
|
|
33
|
+
export async function performSessionRun({ sessionMeta, runOptions, mode, browserConfig, cwd, log, write, version, notifications, browserDeps, muteStdout = false, signal, }) {
|
|
33
34
|
const writeInline = (chunk) => {
|
|
34
35
|
// Keep session logs intact while still echoing inline output to the user.
|
|
35
36
|
write(chunk);
|
|
36
37
|
return muteStdout ? true : process.stdout.write(chunk);
|
|
37
38
|
};
|
|
38
39
|
let currentBrowser = browserConfig
|
|
39
|
-
? {
|
|
40
|
+
? {
|
|
41
|
+
config: browserConfig,
|
|
42
|
+
...(mode === "browser" && sessionMeta.browser?.runtime?.submittedPromptHash !== undefined
|
|
43
|
+
? { runtime: { submittedPromptHash: null } }
|
|
44
|
+
: {}),
|
|
45
|
+
}
|
|
40
46
|
: sessionMeta.browser;
|
|
41
47
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
42
48
|
status: "running",
|
|
43
49
|
startedAt: new Date().toISOString(),
|
|
44
50
|
mode,
|
|
45
|
-
...(browserConfig ? { browser:
|
|
51
|
+
...(browserConfig ? { browser: currentBrowser } : {}),
|
|
46
52
|
});
|
|
47
53
|
const notificationSettings = notifications ?? deriveNotificationSettingsFromMetadata(sessionMeta, process.env);
|
|
48
54
|
const modelForStatus = runOptions.model ?? sessionMeta.model;
|
|
55
|
+
const checkBrowserCancellation = () => {
|
|
56
|
+
if (mode === "browser" && signal?.aborted)
|
|
57
|
+
throw new BrowserRunCancelledError();
|
|
58
|
+
};
|
|
59
|
+
const finalizeBrowserCancellation = async (cancellationError) => {
|
|
60
|
+
const completedAt = new Date().toISOString();
|
|
61
|
+
log("Browser run cancelled.");
|
|
62
|
+
if (modelForStatus) {
|
|
63
|
+
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
64
|
+
status: "cancelled",
|
|
65
|
+
completedAt,
|
|
66
|
+
response: { status: "cancelled" },
|
|
67
|
+
error: undefined,
|
|
68
|
+
transport: undefined,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
await sessionStore.updateSession(sessionMeta.id, {
|
|
72
|
+
status: "cancelled",
|
|
73
|
+
completedAt,
|
|
74
|
+
errorMessage: undefined,
|
|
75
|
+
mode,
|
|
76
|
+
browser: browserConfig
|
|
77
|
+
? {
|
|
78
|
+
...currentBrowser,
|
|
79
|
+
config: browserConfig,
|
|
80
|
+
}
|
|
81
|
+
: undefined,
|
|
82
|
+
response: { status: "cancelled" },
|
|
83
|
+
error: undefined,
|
|
84
|
+
transport: undefined,
|
|
85
|
+
});
|
|
86
|
+
await retireCancelledBrowserTarget(sessionMeta.id, recoveryCaptureFromRuntime(currentBrowser?.runtime), log);
|
|
87
|
+
throw cancellationError;
|
|
88
|
+
};
|
|
49
89
|
try {
|
|
50
90
|
if (mode === "browser") {
|
|
51
91
|
if (!browserConfig) {
|
|
@@ -79,13 +119,17 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
79
119
|
browserConfig,
|
|
80
120
|
cwd,
|
|
81
121
|
log,
|
|
122
|
+
signal,
|
|
82
123
|
}, runnerDeps);
|
|
124
|
+
checkBrowserCancellation();
|
|
83
125
|
const writtenOutputPath = await writeAssistantOutput(runOptions.writeOutputPath, result.answerText ?? "", log);
|
|
126
|
+
checkBrowserCancellation();
|
|
84
127
|
const outputArtifacts = await copyBrowserOutputArtifacts({
|
|
85
128
|
outputPath: writtenOutputPath,
|
|
86
129
|
savedFiles: runOptions.writeArtifacts ? result.savedFiles : undefined,
|
|
87
130
|
log,
|
|
88
131
|
});
|
|
132
|
+
checkBrowserCancellation();
|
|
89
133
|
const browserWarnings = [...(result.warnings ?? []), ...outputArtifacts.warnings];
|
|
90
134
|
await sendSessionNotification({
|
|
91
135
|
sessionId: sessionMeta.id,
|
|
@@ -95,13 +139,16 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
95
139
|
usage: result.usage,
|
|
96
140
|
characters: result.answerText?.length,
|
|
97
141
|
}, notificationSettings, log, result.answerText?.slice(0, 140));
|
|
142
|
+
checkBrowserCancellation();
|
|
98
143
|
if (modelForStatus) {
|
|
99
144
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
100
145
|
status: "completed",
|
|
101
146
|
completedAt: new Date().toISOString(),
|
|
102
147
|
usage: result.usage,
|
|
103
148
|
});
|
|
149
|
+
checkBrowserCancellation();
|
|
104
150
|
}
|
|
151
|
+
checkBrowserCancellation();
|
|
105
152
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
106
153
|
status: "completed",
|
|
107
154
|
completedAt: new Date().toISOString(),
|
|
@@ -400,6 +447,9 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
400
447
|
});
|
|
401
448
|
}
|
|
402
449
|
catch (error) {
|
|
450
|
+
if (error instanceof BrowserRunCancelledError) {
|
|
451
|
+
return await finalizeBrowserCancellation(error);
|
|
452
|
+
}
|
|
403
453
|
const message = formatError(error);
|
|
404
454
|
log(`ERROR: ${message}`);
|
|
405
455
|
markErrorLogged(error);
|
|
@@ -425,6 +475,11 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
425
475
|
const runtime = userError.details
|
|
426
476
|
?.runtime;
|
|
427
477
|
const recoverableRuntime = runtime ?? currentBrowser?.runtime;
|
|
478
|
+
currentBrowser = {
|
|
479
|
+
...currentBrowser,
|
|
480
|
+
config: browserConfig,
|
|
481
|
+
runtime: recoverableRuntime,
|
|
482
|
+
};
|
|
428
483
|
if (!hasRecoverableChatGptConversation(recoverableRuntime) &&
|
|
429
484
|
recoverableRuntime?.promptSubmitted !== true) {
|
|
430
485
|
log(dim("Chrome disconnected before a ChatGPT conversation was created; marking session error."));
|
|
@@ -494,22 +549,32 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
494
549
|
const connectionLostIntervalMs = configuredIntervalMs > 0
|
|
495
550
|
? configuredIntervalMs
|
|
496
551
|
: Math.max(1_000, Math.min(browserConfig?.timeoutMs ?? 30_000, 30_000));
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
552
|
+
let success;
|
|
553
|
+
try {
|
|
554
|
+
success = await autoReattachUntilComplete({
|
|
555
|
+
sessionMeta,
|
|
556
|
+
runtime: recoverableRuntime ?? undefined,
|
|
557
|
+
browserConfig: {
|
|
558
|
+
...browserConfig,
|
|
559
|
+
autoReattachIntervalMs: connectionLostIntervalMs,
|
|
560
|
+
autoReattachDelayMs: browserConfig?.autoReattachDelayMs ?? 0,
|
|
561
|
+
autoReattachTimeoutMs: browserConfig?.autoReattachTimeoutMs ?? browserConfig?.timeoutMs ?? 120_000,
|
|
562
|
+
},
|
|
563
|
+
browserMetadata: currentBrowser,
|
|
564
|
+
runOptions,
|
|
565
|
+
modelForStatus,
|
|
566
|
+
notificationSettings,
|
|
567
|
+
log,
|
|
568
|
+
maxAttempts: configuredIntervalMs > 0 ? undefined : 1,
|
|
569
|
+
signal,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
catch (recoveryError) {
|
|
573
|
+
if (signal?.aborted || recoveryError instanceof BrowserRunCancelledError) {
|
|
574
|
+
return await finalizeBrowserCancellation(new BrowserRunCancelledError());
|
|
575
|
+
}
|
|
576
|
+
throw recoveryError;
|
|
577
|
+
}
|
|
513
578
|
if (success) {
|
|
514
579
|
return;
|
|
515
580
|
}
|
|
@@ -530,6 +595,11 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
530
595
|
};
|
|
531
596
|
const autoReattachIntervalMs = browserConfig?.autoReattachIntervalMs ?? 0;
|
|
532
597
|
const autoRuntime = runtime ?? currentBrowser?.runtime;
|
|
598
|
+
currentBrowser = {
|
|
599
|
+
...currentBrowser,
|
|
600
|
+
config: browserConfig,
|
|
601
|
+
runtime: autoRuntime,
|
|
602
|
+
};
|
|
533
603
|
const willAutoReattach = autoReattachIntervalMs > 0 && Boolean(autoRuntime);
|
|
534
604
|
if (willAutoReattach) {
|
|
535
605
|
if (modelForStatus) {
|
|
@@ -553,16 +623,26 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
553
623
|
response: timeoutResponse,
|
|
554
624
|
error: timeoutError,
|
|
555
625
|
});
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
626
|
+
let success;
|
|
627
|
+
try {
|
|
628
|
+
success = await autoReattachUntilComplete({
|
|
629
|
+
sessionMeta,
|
|
630
|
+
runtime: autoRuntime,
|
|
631
|
+
browserConfig,
|
|
632
|
+
browserMetadata: currentBrowser,
|
|
633
|
+
runOptions,
|
|
634
|
+
modelForStatus,
|
|
635
|
+
notificationSettings,
|
|
636
|
+
log,
|
|
637
|
+
signal,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
catch (recoveryError) {
|
|
641
|
+
if (signal?.aborted || recoveryError instanceof BrowserRunCancelledError) {
|
|
642
|
+
return await finalizeBrowserCancellation(new BrowserRunCancelledError());
|
|
643
|
+
}
|
|
644
|
+
throw recoveryError;
|
|
645
|
+
}
|
|
566
646
|
if (success) {
|
|
567
647
|
return;
|
|
568
648
|
}
|
|
@@ -975,7 +1055,7 @@ async function writeAssistantOutput(targetPath, content, log) {
|
|
|
975
1055
|
log(dim(`write-output failed (${reason}); session completed anyway.`));
|
|
976
1056
|
}
|
|
977
1057
|
}
|
|
978
|
-
async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig, browserMetadata, runOptions, modelForStatus, notificationSettings, log, maxAttempts, }) {
|
|
1058
|
+
async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig, browserMetadata, runOptions, modelForStatus, notificationSettings, log, maxAttempts, signal, }) {
|
|
979
1059
|
if (!runtime || !browserConfig) {
|
|
980
1060
|
log(dim("Auto-reattach disabled: missing runtime or browser config."));
|
|
981
1061
|
return false;
|
|
@@ -990,12 +1070,16 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
990
1070
|
120_000;
|
|
991
1071
|
const maxTotalMs = 2 * 60 * 60 * 1000; // 2h hard cap; avoid infinite polling by default.
|
|
992
1072
|
const maxDeadline = Date.now() + maxTotalMs;
|
|
1073
|
+
const checkCancellation = () => {
|
|
1074
|
+
if (signal?.aborted)
|
|
1075
|
+
throw new BrowserRunCancelledError();
|
|
1076
|
+
};
|
|
993
1077
|
const attemptLimit = typeof maxAttempts === "number" && maxAttempts > 0
|
|
994
1078
|
? Math.floor(maxAttempts)
|
|
995
1079
|
: Number.POSITIVE_INFINITY;
|
|
996
1080
|
if (delayMs > 0) {
|
|
997
1081
|
log(dim(`Auto-reattach starting in ${formatElapsed(delayMs)}...`));
|
|
998
|
-
await
|
|
1082
|
+
await delay(delayMs, signal);
|
|
999
1083
|
}
|
|
1000
1084
|
if (Number.isFinite(attemptLimit)) {
|
|
1001
1085
|
log(dim(`Auto-reattach will try up to ${attemptLimit} attempt(s).`));
|
|
@@ -1011,6 +1095,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
1011
1095
|
logger.verbose = true;
|
|
1012
1096
|
let attempt = 0;
|
|
1013
1097
|
for (;;) {
|
|
1098
|
+
checkCancellation();
|
|
1014
1099
|
const remainingBudgetMs = maxDeadline - Date.now();
|
|
1015
1100
|
if (remainingBudgetMs <= 0) {
|
|
1016
1101
|
log(dim(`Auto-reattach stopped after ${formatElapsed(maxTotalMs)} without capturing an answer.`));
|
|
@@ -1024,9 +1109,11 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
1024
1109
|
...browserConfig,
|
|
1025
1110
|
timeoutMs,
|
|
1026
1111
|
};
|
|
1027
|
-
const result = await resumeBrowserSession(runtime, reattachConfig, logger, {
|
|
1112
|
+
const result = await awaitBrowserCancellation(resumeBrowserSession(runtime, reattachConfig, logger, {
|
|
1028
1113
|
promptPreview: sessionMeta.promptPreview,
|
|
1029
|
-
|
|
1114
|
+
signal,
|
|
1115
|
+
}), signal);
|
|
1116
|
+
checkCancellation();
|
|
1030
1117
|
captureSucceeded = true;
|
|
1031
1118
|
const answerText = result.answerMarkdown || result.answerText || "";
|
|
1032
1119
|
const outputTokens = estimateTokenCount(answerText);
|
|
@@ -1039,11 +1126,11 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
1039
1126
|
existingArtifacts: sessionMeta.artifacts,
|
|
1040
1127
|
logger,
|
|
1041
1128
|
});
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1129
|
+
checkCancellation();
|
|
1130
|
+
const paths = await sessionStore.getPaths(sessionMeta.id);
|
|
1131
|
+
checkCancellation();
|
|
1132
|
+
await fs.appendFile(paths.log, `[auto-reattach] captured assistant response on attempt ${attempt}\nAnswer:\n${answerText}\n`, "utf8");
|
|
1133
|
+
checkCancellation();
|
|
1047
1134
|
if (modelForStatus) {
|
|
1048
1135
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
1049
1136
|
status: "completed",
|
|
@@ -1055,8 +1142,10 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
1055
1142
|
totalTokens: outputTokens,
|
|
1056
1143
|
},
|
|
1057
1144
|
});
|
|
1145
|
+
checkCancellation();
|
|
1058
1146
|
}
|
|
1059
1147
|
await writeAssistantOutput(runOptions.writeOutputPath, answerText, log);
|
|
1148
|
+
checkCancellation();
|
|
1060
1149
|
await sendSessionNotification({
|
|
1061
1150
|
sessionId: sessionMeta.id,
|
|
1062
1151
|
sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
|
|
@@ -1068,6 +1157,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
1068
1157
|
},
|
|
1069
1158
|
characters: answerText.length,
|
|
1070
1159
|
}, notificationSettings, log, answerText.slice(0, 140));
|
|
1160
|
+
checkCancellation();
|
|
1071
1161
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
1072
1162
|
status: "completed",
|
|
1073
1163
|
completedAt: new Date().toISOString(),
|
|
@@ -1089,9 +1179,13 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
1089
1179
|
transport: undefined,
|
|
1090
1180
|
});
|
|
1091
1181
|
log(kleur.green("Auto-reattach succeeded; session marked completed."));
|
|
1182
|
+
await retireRecoveredBrowserTarget(sessionMeta.id, result.captureTarget, logger);
|
|
1092
1183
|
return true;
|
|
1093
1184
|
}
|
|
1094
1185
|
catch (error) {
|
|
1186
|
+
if (signal?.aborted || error instanceof BrowserRunCancelledError) {
|
|
1187
|
+
throw new BrowserRunCancelledError();
|
|
1188
|
+
}
|
|
1095
1189
|
if (captureSucceeded) {
|
|
1096
1190
|
const message = formatError(error);
|
|
1097
1191
|
if (modelForStatus) {
|
|
@@ -1129,7 +1223,25 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
1129
1223
|
log(dim(`Auto-reattach stopped after ${formatElapsed(maxTotalMs)} without capturing an answer.`));
|
|
1130
1224
|
return false;
|
|
1131
1225
|
}
|
|
1132
|
-
await
|
|
1226
|
+
await delay(Math.min(intervalMs, remainingAfterAttemptMs), signal);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
async function awaitBrowserCancellation(task, signal) {
|
|
1230
|
+
if (!signal)
|
|
1231
|
+
return task;
|
|
1232
|
+
if (signal.aborted)
|
|
1233
|
+
throw new BrowserRunCancelledError();
|
|
1234
|
+
let abort;
|
|
1235
|
+
const cancelled = new Promise((_, reject) => {
|
|
1236
|
+
abort = () => reject(new BrowserRunCancelledError());
|
|
1237
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
1238
|
+
});
|
|
1239
|
+
try {
|
|
1240
|
+
return await Promise.race([task, cancelled]);
|
|
1241
|
+
}
|
|
1242
|
+
finally {
|
|
1243
|
+
if (abort)
|
|
1244
|
+
signal.removeEventListener("abort", abort);
|
|
1133
1245
|
}
|
|
1134
1246
|
}
|
|
1135
1247
|
export function deriveModelOutputPath(basePath, model) {
|
|
@@ -74,9 +74,9 @@ const consultInputShape = {
|
|
|
74
74
|
.optional()
|
|
75
75
|
.describe("Browser-only: model picker strategy. Mirrors the CLI --browser-model-strategy flag."),
|
|
76
76
|
browserResearchMode: z
|
|
77
|
-
.enum(["deep"])
|
|
77
|
+
.enum(["search", "deep"])
|
|
78
78
|
.optional()
|
|
79
|
-
.describe("Browser-only:
|
|
79
|
+
.describe("Browser-only: explicitly select ChatGPT Web Search or Deep Research."),
|
|
80
80
|
browserArchive: z
|
|
81
81
|
.enum(["auto", "always", "never"])
|
|
82
82
|
.optional()
|
package/dist/src/mcp/types.js
CHANGED
|
@@ -23,7 +23,7 @@ export const consultInputSchema = z
|
|
|
23
23
|
browserBundleFormat: z.enum(["auto", "text", "zip"]).optional(),
|
|
24
24
|
browserThinkingTime: browserThinkingTimeInputSchema.optional(),
|
|
25
25
|
browserModelStrategy: z.enum(["select", "current", "ignore"]).optional(),
|
|
26
|
-
browserResearchMode: z.enum(["deep"]).optional(),
|
|
26
|
+
browserResearchMode: z.enum(["search", "deep"]).optional(),
|
|
27
27
|
browserArchive: z.enum(["auto", "always", "never"]).optional(),
|
|
28
28
|
browserFollowUps: z.array(z.string()).optional(),
|
|
29
29
|
browserKeepBrowser: z.boolean().optional(),
|
|
@@ -30,6 +30,20 @@ const countTokensAnthropic = (input) => {
|
|
|
30
30
|
// supported limit at the base-rate boundary until cost estimation supports tiers.
|
|
31
31
|
const GPT_5_6_BASE_RATE_INPUT_LIMIT = 272_000;
|
|
32
32
|
export const MODEL_CONFIGS = {
|
|
33
|
+
"gpt-6-astra": {
|
|
34
|
+
model: "gpt-6-astra",
|
|
35
|
+
provider: "openai",
|
|
36
|
+
tokenizer: countTokensGpt5,
|
|
37
|
+
// Conservative base-rate budget, not Astra's maximum context window.
|
|
38
|
+
// https://developers.openai.com/api/docs/models/gpt-6-astra
|
|
39
|
+
inputLimit: 272_000,
|
|
40
|
+
pricing: {
|
|
41
|
+
inputPerToken: 10 / 1_000_000,
|
|
42
|
+
outputPerToken: 50 / 1_000_000,
|
|
43
|
+
},
|
|
44
|
+
reasoning: { effort: "xhigh" },
|
|
45
|
+
searchToolType: "web_search",
|
|
46
|
+
},
|
|
33
47
|
"gpt-5.6": {
|
|
34
48
|
model: "gpt-5.6",
|
|
35
49
|
provider: "openai",
|
package/dist/src/oracle/run.js
CHANGED
|
@@ -36,6 +36,15 @@ const DEFAULT_TIMEOUT_PRO_MS = 60 * 60 * 1000;
|
|
|
36
36
|
const GPT_5_6_API_MODELS = new Set(["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]);
|
|
37
37
|
const REASONING_EFFORTS = new Set(["none", "low", "medium", "high", "xhigh", "max"]);
|
|
38
38
|
const REASONING_MODES = new Set(["standard", "pro"]);
|
|
39
|
+
// Astra has a narrower effort range than the GPT-5.6 API family. Keep this
|
|
40
|
+
// capability separate from GPT_5_6_API_MODELS so adding a model cannot
|
|
41
|
+
// accidentally inherit a family's exact option set.
|
|
42
|
+
const MODEL_REASONING_CAPABILITIES = new Map([
|
|
43
|
+
[
|
|
44
|
+
"gpt-6-astra",
|
|
45
|
+
{ efforts: new Set(["low", "medium", "high", "xhigh", "max"]), modes: REASONING_MODES },
|
|
46
|
+
],
|
|
47
|
+
]);
|
|
39
48
|
const defaultWait = (ms) => new Promise((resolve) => {
|
|
40
49
|
setTimeout(resolve, ms);
|
|
41
50
|
});
|
|
@@ -72,9 +81,13 @@ function runtimeKeySource({ route, providerMode, optionsApiKey, }) {
|
|
|
72
81
|
return "XAI_API_KEY";
|
|
73
82
|
return optionsApiKey ? "apiKey option" : route.keySource;
|
|
74
83
|
}
|
|
75
|
-
function validateReasoningOptions(options, route) {
|
|
84
|
+
function validateReasoningOptions(options, route, modelConfig) {
|
|
76
85
|
const { reasoningEffort, reasoningMode } = options;
|
|
77
|
-
|
|
86
|
+
const capabilities = GPT_5_6_API_MODELS.has(options.model)
|
|
87
|
+
? { efforts: REASONING_EFFORTS, modes: REASONING_MODES }
|
|
88
|
+
: MODEL_REASONING_CAPABILITIES.get(options.model);
|
|
89
|
+
const effectiveEffort = reasoningEffort ?? modelConfig?.reasoning?.effort;
|
|
90
|
+
if (!reasoningEffort && !reasoningMode && !effectiveEffort)
|
|
78
91
|
return;
|
|
79
92
|
if (reasoningEffort && !REASONING_EFFORTS.has(reasoningEffort)) {
|
|
80
93
|
throw new PromptValidationError(`Invalid reasoning effort "${reasoningEffort}". Expected none, low, medium, high, xhigh, or max.`, { model: options.model, reasoningEffort });
|
|
@@ -82,14 +95,20 @@ function validateReasoningOptions(options, route) {
|
|
|
82
95
|
if (reasoningMode && !REASONING_MODES.has(reasoningMode)) {
|
|
83
96
|
throw new PromptValidationError(`Invalid reasoning mode "${reasoningMode}". Expected standard or pro.`, { model: options.model, reasoningMode });
|
|
84
97
|
}
|
|
85
|
-
if (
|
|
98
|
+
if ((reasoningEffort || reasoningMode) && !capabilities) {
|
|
86
99
|
const option = reasoningMode
|
|
87
100
|
? `Reasoning mode "${reasoningMode}"`
|
|
88
101
|
: `Reasoning effort "${reasoningEffort}"`;
|
|
89
102
|
const guidance = reasoningMode
|
|
90
103
|
? `Use --model gpt-5.6-sol --reasoning-mode ${reasoningMode}.`
|
|
91
104
|
: `Use --model gpt-5.6-sol --reasoning-effort ${reasoningEffort}.`;
|
|
92
|
-
throw new PromptValidationError(`${option} is available only for GPT-5.6 API models. ${guidance}`, { model: options.model, reasoningEffort, reasoningMode });
|
|
105
|
+
throw new PromptValidationError(`${option} is available only for GPT-5.6 or GPT-6 Astra API models. ${guidance}`, { model: options.model, reasoningEffort, reasoningMode });
|
|
106
|
+
}
|
|
107
|
+
if (effectiveEffort && capabilities && !capabilities.efforts.has(effectiveEffort)) {
|
|
108
|
+
throw new PromptValidationError(`Reasoning effort "${effectiveEffort}" is not supported for ${options.model}. Expected low, medium, high, xhigh, or max.`, { model: options.model, reasoningEffort: effectiveEffort, reasoningMode });
|
|
109
|
+
}
|
|
110
|
+
if (reasoningMode && capabilities && !capabilities.modes.has(reasoningMode)) {
|
|
111
|
+
throw new PromptValidationError(`Reasoning mode "${reasoningMode}" is not supported for ${options.model}. Expected standard or pro.`, { model: options.model, reasoningEffort, reasoningMode });
|
|
93
112
|
}
|
|
94
113
|
if (reasoningMode &&
|
|
95
114
|
!route.isAzureOpenAI &&
|
|
@@ -166,6 +185,10 @@ export async function runOracle(options, deps = {}) {
|
|
|
166
185
|
openRouterApiKey: resolverOpenRouterApiKey,
|
|
167
186
|
modelOverrides: options.modelOverrides,
|
|
168
187
|
});
|
|
188
|
+
// Validate the resolved default as well as explicit flags. This catches a
|
|
189
|
+
// model override such as Astra + reasoning.effort=none, while allowing an
|
|
190
|
+
// explicit supported effort to replace an invalid bundled/overridden default.
|
|
191
|
+
validateReasoningOptions(options, route, modelConfig);
|
|
169
192
|
const isLongRunningModel = isProTierModel;
|
|
170
193
|
const supportsBackground = modelConfig.supportsBackground !== false;
|
|
171
194
|
const useBackground = supportsBackground ? (options.background ?? isLongRunningModel) : false;
|
|
@@ -13,6 +13,9 @@ import { BrowserRunCancelledError } from "../oracle/errors.js";
|
|
|
13
13
|
export function createRemoteBrowserExecutor({ host, token }) {
|
|
14
14
|
// Return a drop-in replacement for runBrowserMode so the browser session runner can stay unchanged.
|
|
15
15
|
return async function remoteBrowserExecutor(options) {
|
|
16
|
+
if (options.config?.researchMode === "search") {
|
|
17
|
+
throw new Error("Web Search is a local browser pilot; --remote-host does not negotiate this capability yet. Use local Chrome or --browser-attach-running.");
|
|
18
|
+
}
|
|
16
19
|
const callerSignal = options.signal;
|
|
17
20
|
if (callerSignal?.aborted)
|
|
18
21
|
throw new BrowserRunCancelledError("Browser run cancelled before the request was sent.");
|
|
@@ -814,10 +814,12 @@ function sanitizeResult(result, warnings = []) {
|
|
|
814
814
|
answerChars: result.answerChars,
|
|
815
815
|
modelSelection: result.modelSelection,
|
|
816
816
|
thinkingSelection: result.thinkingSelection,
|
|
817
|
+
researchPlan: result.researchPlan,
|
|
817
818
|
archive: result.archive,
|
|
818
819
|
tabUrl: result.tabUrl,
|
|
819
820
|
conversationId: result.conversationId,
|
|
820
821
|
promptSubmitted: result.promptSubmitted,
|
|
822
|
+
submittedPromptHash: result.submittedPromptHash,
|
|
821
823
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
822
824
|
chromePid: undefined,
|
|
823
825
|
chromePort: undefined,
|
|
@@ -304,7 +304,14 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
|
|
|
304
304
|
})),
|
|
305
305
|
cwd,
|
|
306
306
|
mode,
|
|
307
|
-
browser:
|
|
307
|
+
browser: mode === "browser"
|
|
308
|
+
? {
|
|
309
|
+
...(browserConfig ? { config: browserConfig } : {}),
|
|
310
|
+
runtime: { submittedPromptHash: null },
|
|
311
|
+
}
|
|
312
|
+
: browserConfig
|
|
313
|
+
? { config: browserConfig }
|
|
314
|
+
: undefined,
|
|
308
315
|
notifications,
|
|
309
316
|
options: {
|
|
310
317
|
prompt: options.prompt,
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steipete/oracle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "CLI wrapper around OpenAI Responses API with GPT-5.6 Sol, GPT-5.6, GPT-5.5 Pro, GPT-5.5, GPT-5.4, GPT-5.2, GPT-5.1, and GPT-5.1 Codex high reasoning modes.",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://askoracle.sh",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@google/genai": "^2.21.0",
|
|
62
62
|
"@google/generative-ai": "^0.24.1",
|
|
63
63
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
64
|
-
"@steipete/sweet-cookie": "^0.4.
|
|
64
|
+
"@steipete/sweet-cookie": "^0.4.3",
|
|
65
65
|
"chalk": "^6.0.0",
|
|
66
66
|
"chrome-launcher": "^1.2.1",
|
|
67
67
|
"chrome-remote-interface": "^0.34.0",
|
|
@@ -70,17 +70,17 @@
|
|
|
70
70
|
"dotenv": "^17.4.2",
|
|
71
71
|
"fast-glob": "^3.3.3",
|
|
72
72
|
"gpt-tokenizer": "^4.0.0",
|
|
73
|
-
"inquirer": "14.2.
|
|
73
|
+
"inquirer": "14.2.2",
|
|
74
74
|
"json5": "^2.2.3",
|
|
75
75
|
"kleur": "^4.1.5",
|
|
76
76
|
"markdansi": "0.3.3",
|
|
77
|
-
"openai": "^7.
|
|
77
|
+
"openai": "^7.13.0",
|
|
78
78
|
"osc-progress": "^0.3.3",
|
|
79
79
|
"qs": "^6.16.0",
|
|
80
80
|
"shiki": "^4.4.3",
|
|
81
81
|
"toasted-notifier": "^10.1.0",
|
|
82
|
-
"tokentally": "^0.1.
|
|
83
|
-
"zod": "^4.
|
|
82
|
+
"tokentally": "^0.1.6",
|
|
83
|
+
"zod": "^4.6.0"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@anthropic-ai/tokenizer": "^0.0.4",
|
|
@@ -88,13 +88,13 @@
|
|
|
88
88
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
89
89
|
"@types/chrome-remote-interface": "^0.34.0",
|
|
90
90
|
"@types/inquirer": "^9.0.10",
|
|
91
|
-
"@types/node": "^26.
|
|
91
|
+
"@types/node": "^26.5.1",
|
|
92
92
|
"@vitest/coverage-v8": "5.0.0",
|
|
93
|
-
"devtools-protocol": "0.0.
|
|
93
|
+
"devtools-protocol": "0.0.1694333",
|
|
94
94
|
"es-toolkit": "^1.52.0",
|
|
95
95
|
"esbuild": "^0.28.2",
|
|
96
|
-
"oxfmt": "0.
|
|
97
|
-
"oxlint": "^1.
|
|
96
|
+
"oxfmt": "0.67.0",
|
|
97
|
+
"oxlint": "^1.82.0",
|
|
98
98
|
"puppeteer-core": "^25.10.0",
|
|
99
99
|
"tsx": "^4.23.13",
|
|
100
100
|
"typescript": "^7.0.2",
|
|
Binary file
|
|
Binary file
|