@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
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { readFiles, createFileSections, MODEL_CONFIGS, TOKENIZER_OPTIONS, formatFileSections, } from "../oracle.js";
|
|
4
|
+
import { readFiles, createFileSections, FileValidationError, MODEL_CONFIGS, TOKENIZER_OPTIONS, formatFileSections, } from "../oracle.js";
|
|
5
5
|
import { isKnownModel } from "../oracle/modelResolver.js";
|
|
6
6
|
import { buildPromptMarkdown } from "../oracle/promptAssembly.js";
|
|
7
7
|
import { buildAttachmentPlan } from "./policies.js";
|
|
8
8
|
import { createStoredZip } from "./zipBundle.js";
|
|
9
9
|
const DEFAULT_BROWSER_INLINE_CHAR_BUDGET = 60_000;
|
|
10
|
+
const MAX_BROWSER_ATTACHMENTS = 10;
|
|
11
|
+
const MAX_BROWSER_ZIP_BUNDLE_BYTES = 128 * 1024 * 1024;
|
|
10
12
|
const MEDIA_EXTENSIONS = new Set([
|
|
11
13
|
".mp4",
|
|
12
14
|
".mov",
|
|
@@ -31,27 +33,95 @@ const MEDIA_EXTENSIONS = new Set([
|
|
|
31
33
|
".heif",
|
|
32
34
|
".pdf",
|
|
33
35
|
]);
|
|
36
|
+
const ARCHIVE_EXTENSIONS = new Set([
|
|
37
|
+
".7z",
|
|
38
|
+
".aab",
|
|
39
|
+
".apk",
|
|
40
|
+
".br",
|
|
41
|
+
".bz2",
|
|
42
|
+
".cab",
|
|
43
|
+
".crx",
|
|
44
|
+
".deb",
|
|
45
|
+
".dmg",
|
|
46
|
+
".doc",
|
|
47
|
+
".docx",
|
|
48
|
+
".ear",
|
|
49
|
+
".epub",
|
|
50
|
+
".gz",
|
|
51
|
+
".ipa",
|
|
52
|
+
".iso",
|
|
53
|
+
".jar",
|
|
54
|
+
".lz",
|
|
55
|
+
".lz4",
|
|
56
|
+
".msi",
|
|
57
|
+
".odp",
|
|
58
|
+
".ods",
|
|
59
|
+
".odt",
|
|
60
|
+
".pkg",
|
|
61
|
+
".ppt",
|
|
62
|
+
".pptx",
|
|
63
|
+
".rar",
|
|
64
|
+
".rpm",
|
|
65
|
+
".tar",
|
|
66
|
+
".tgz",
|
|
67
|
+
".war",
|
|
68
|
+
".whl",
|
|
69
|
+
".xls",
|
|
70
|
+
".xlsx",
|
|
71
|
+
".xz",
|
|
72
|
+
".xpi",
|
|
73
|
+
".zip",
|
|
74
|
+
".zipx",
|
|
75
|
+
".zst",
|
|
76
|
+
]);
|
|
34
77
|
export function isMediaFile(filePath) {
|
|
35
78
|
const ext = path.extname(filePath).toLowerCase();
|
|
36
79
|
return MEDIA_EXTENSIONS.has(ext);
|
|
37
80
|
}
|
|
81
|
+
export function isRawUploadFile(filePath) {
|
|
82
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
83
|
+
return MEDIA_EXTENSIONS.has(ext) || ARCHIVE_EXTENSIONS.has(ext);
|
|
84
|
+
}
|
|
38
85
|
function formatSectionsForBundle(sections, options = {}) {
|
|
39
86
|
return formatFileSections(sections, {
|
|
40
87
|
lineNumbers: options.lineNumbers ?? true,
|
|
41
88
|
trailingNewline: true,
|
|
42
89
|
});
|
|
43
90
|
}
|
|
44
|
-
|
|
91
|
+
function resolveBrowserBundleFormat(format, sources) {
|
|
92
|
+
if (format !== "auto") {
|
|
93
|
+
return format;
|
|
94
|
+
}
|
|
95
|
+
return sources.hasRawUploadFiles ? "zip" : "text";
|
|
96
|
+
}
|
|
97
|
+
function shouldWriteBrowserBundle(format, { attachmentCount, bundleRequested, textSourceCount, textPlanShouldBundle, }) {
|
|
98
|
+
if (format === "zip") {
|
|
99
|
+
return (textPlanShouldBundle ||
|
|
100
|
+
(bundleRequested && attachmentCount > 0) ||
|
|
101
|
+
attachmentCount > MAX_BROWSER_ATTACHMENTS);
|
|
102
|
+
}
|
|
103
|
+
return textSourceCount > 0 && (textPlanShouldBundle || attachmentCount > MAX_BROWSER_ATTACHMENTS);
|
|
104
|
+
}
|
|
105
|
+
function assertAttachmentCount(attachments, format) {
|
|
106
|
+
if (attachments.length <= MAX_BROWSER_ATTACHMENTS)
|
|
107
|
+
return;
|
|
108
|
+
throw new Error(`Browser upload has ${attachments.length} attachments after applying bundle format "${format}". Use --browser-bundle-format auto or zip to stay within the ${MAX_BROWSER_ATTACHMENTS}-attachment limit.`);
|
|
109
|
+
}
|
|
110
|
+
async function writeBrowserBundle(sections, sources, format) {
|
|
45
111
|
const bundleDir = await fs.mkdtemp(path.join(os.tmpdir(), "oracle-browser-bundle-"));
|
|
46
112
|
const tokenEstimateText = formatSectionsForBundle(sections, {
|
|
47
113
|
lineNumbers: format === "text",
|
|
48
114
|
});
|
|
49
115
|
if (format === "zip") {
|
|
116
|
+
const totalSourceBytes = sources.reduce((total, source) => total + source.sizeBytes, 0);
|
|
117
|
+
if (totalSourceBytes > MAX_BROWSER_ZIP_BUNDLE_BYTES) {
|
|
118
|
+
throw new Error(`Browser ZIP bundle inputs exceed the ${MAX_BROWSER_ZIP_BUNDLE_BYTES}-byte in-memory limit.`);
|
|
119
|
+
}
|
|
50
120
|
const bundlePath = path.join(bundleDir, "attachments-bundle.zip");
|
|
51
|
-
const buffer = createStoredZip(
|
|
52
|
-
path:
|
|
53
|
-
content:
|
|
54
|
-
})));
|
|
121
|
+
const buffer = createStoredZip(await Promise.all(sources.map(async (source) => ({
|
|
122
|
+
path: source.displayPath,
|
|
123
|
+
content: await fs.readFile(source.absolutePath),
|
|
124
|
+
}))));
|
|
55
125
|
await fs.writeFile(bundlePath, buffer);
|
|
56
126
|
return {
|
|
57
127
|
attachment: {
|
|
@@ -60,7 +130,7 @@ async function writeBrowserBundle(sections, format) {
|
|
|
60
130
|
sizeBytes: buffer.length,
|
|
61
131
|
generatedBundle: true,
|
|
62
132
|
},
|
|
63
|
-
metadata: { originalCount:
|
|
133
|
+
metadata: { originalCount: sources.length, bundlePath, format },
|
|
64
134
|
tokenEstimateText,
|
|
65
135
|
};
|
|
66
136
|
}
|
|
@@ -81,11 +151,27 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
81
151
|
const cwd = deps.cwd ?? process.cwd();
|
|
82
152
|
const readFilesFn = deps.readFilesImpl ?? readFiles;
|
|
83
153
|
const allFilePaths = runOptions.file ?? [];
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
154
|
+
const discoveredFiles = allFilePaths.length > 0
|
|
155
|
+
? await readFilesFn(allFilePaths, {
|
|
156
|
+
cwd,
|
|
157
|
+
maxFileSizeBytes: 0,
|
|
158
|
+
readContents: false,
|
|
159
|
+
})
|
|
160
|
+
: [];
|
|
161
|
+
const textFilePaths = discoveredFiles
|
|
162
|
+
.filter((file) => !isRawUploadFile(file.path))
|
|
163
|
+
.map((file) => file.path);
|
|
164
|
+
const rawUploadFiles = discoveredFiles.filter((file) => isRawUploadFile(file.path));
|
|
165
|
+
const maxFileSizeBytes = runOptions.maxFileSizeBytes;
|
|
166
|
+
const rawUploadAttachments = await Promise.all(rawUploadFiles.map(async ({ path: filePath }) => {
|
|
87
167
|
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
88
168
|
const stats = await fs.stat(resolvedPath);
|
|
169
|
+
if (maxFileSizeBytes && stats.size > maxFileSizeBytes) {
|
|
170
|
+
throw new FileValidationError(`The following file exceeds the ${maxFileSizeBytes}-byte limit:\n- ${path.relative(cwd, resolvedPath) || resolvedPath} (${stats.size} bytes)`, {
|
|
171
|
+
files: [resolvedPath],
|
|
172
|
+
limitBytes: maxFileSizeBytes,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
89
175
|
return {
|
|
90
176
|
path: resolvedPath,
|
|
91
177
|
displayPath: path.relative(cwd, resolvedPath) || path.basename(resolvedPath),
|
|
@@ -105,7 +191,10 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
105
191
|
? "never"
|
|
106
192
|
: (runOptions.browserAttachments ?? "auto");
|
|
107
193
|
const bundleRequested = Boolean(runOptions.browserBundleFiles);
|
|
108
|
-
const bundleFormat = runOptions.browserBundleFormat ?? "
|
|
194
|
+
const bundleFormat = runOptions.browserBundleFormat ?? "auto";
|
|
195
|
+
if (attachmentsPolicy === "never" && rawUploadAttachments.length > 0) {
|
|
196
|
+
throw new FileValidationError("Raw or binary files cannot be pasted inline when browser attachments are disabled. Use --browser-attachments auto or always.", { files: rawUploadAttachments.map((attachment) => attachment.displayPath) });
|
|
197
|
+
}
|
|
109
198
|
const inlinePlan = buildAttachmentPlan(sections, { inlineFiles: true, bundleRequested });
|
|
110
199
|
const uploadPlan = buildAttachmentPlan(sections, { inlineFiles: false, bundleRequested });
|
|
111
200
|
const baseComposerSections = [];
|
|
@@ -124,25 +213,47 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
124
213
|
: inlineComposerText.length <= DEFAULT_BROWSER_INLINE_CHAR_BUDGET || sections.length === 0
|
|
125
214
|
? inlinePlan
|
|
126
215
|
: uploadPlan;
|
|
127
|
-
const
|
|
216
|
+
const textBundleSources = sections.map((section) => ({
|
|
217
|
+
absolutePath: section.absolutePath,
|
|
218
|
+
displayPath: section.displayPath,
|
|
219
|
+
sizeBytes: Buffer.byteLength(section.content, "utf8"),
|
|
220
|
+
}));
|
|
221
|
+
const rawUploadBundleSources = rawUploadAttachments.map((attachment) => ({
|
|
222
|
+
absolutePath: attachment.path,
|
|
223
|
+
displayPath: attachment.displayPath,
|
|
224
|
+
sizeBytes: attachment.sizeBytes ?? 0,
|
|
225
|
+
}));
|
|
226
|
+
const allBundleSources = [...textBundleSources, ...rawUploadBundleSources];
|
|
227
|
+
const attachments = [...selectedPlan.attachments, ...rawUploadAttachments];
|
|
228
|
+
const resolvedBundleFormat = resolveBrowserBundleFormat(bundleFormat, {
|
|
229
|
+
hasRawUploadFiles: rawUploadAttachments.length > 0,
|
|
230
|
+
});
|
|
231
|
+
const shouldBundle = shouldWriteBrowserBundle(resolvedBundleFormat, {
|
|
232
|
+
attachmentCount: attachments.length,
|
|
233
|
+
bundleRequested,
|
|
234
|
+
textSourceCount: textBundleSources.length,
|
|
235
|
+
textPlanShouldBundle: selectedPlan.shouldBundle,
|
|
236
|
+
});
|
|
237
|
+
const composerText = (!shouldBundle && selectedPlan.inlineBlock
|
|
128
238
|
? [...baseComposerSections, selectedPlan.inlineBlock]
|
|
129
239
|
: baseComposerSections)
|
|
130
240
|
.filter(Boolean)
|
|
131
241
|
.join("\n\n")
|
|
132
242
|
.trim();
|
|
133
|
-
const attachments = [...selectedPlan.attachments, ...mediaAttachments];
|
|
134
|
-
const shouldBundle = selectedPlan.shouldBundle;
|
|
135
243
|
let bundleText = null;
|
|
136
244
|
let bundled = null;
|
|
137
245
|
if (shouldBundle) {
|
|
138
|
-
const writtenBundle = await writeBrowserBundle(sections,
|
|
246
|
+
const writtenBundle = await writeBrowserBundle(sections, resolvedBundleFormat === "zip" ? allBundleSources : textBundleSources, resolvedBundleFormat);
|
|
139
247
|
bundleText = writtenBundle.tokenEstimateText;
|
|
140
248
|
attachments.length = 0;
|
|
141
249
|
attachments.push(writtenBundle.attachment);
|
|
142
|
-
|
|
250
|
+
if (resolvedBundleFormat === "text") {
|
|
251
|
+
attachments.push(...rawUploadAttachments);
|
|
252
|
+
}
|
|
143
253
|
bundled = writtenBundle.metadata;
|
|
144
254
|
}
|
|
145
|
-
|
|
255
|
+
assertAttachmentCount(attachments, resolvedBundleFormat);
|
|
256
|
+
const inlineFileCount = shouldBundle ? 0 : selectedPlan.inlineFileCount;
|
|
146
257
|
const modelConfig = isKnownModel(runOptions.model)
|
|
147
258
|
? MODEL_CONFIGS[runOptions.model]
|
|
148
259
|
: MODEL_CONFIGS["gpt-5.1"];
|
|
@@ -167,15 +278,27 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
167
278
|
let fallback = null;
|
|
168
279
|
if (attachmentsPolicy === "auto" && selectedPlan.mode === "inline" && sections.length > 0) {
|
|
169
280
|
const fallbackComposerText = baseComposerSections.join("\n\n").trim();
|
|
170
|
-
const fallbackAttachments = [...uploadPlan.attachments, ...
|
|
281
|
+
const fallbackAttachments = [...uploadPlan.attachments, ...rawUploadAttachments];
|
|
171
282
|
let fallbackBundled = null;
|
|
172
|
-
|
|
173
|
-
|
|
283
|
+
const fallbackBundleFormat = resolveBrowserBundleFormat(bundleFormat, {
|
|
284
|
+
hasRawUploadFiles: rawUploadAttachments.length > 0,
|
|
285
|
+
});
|
|
286
|
+
const fallbackShouldBundle = shouldWriteBrowserBundle(fallbackBundleFormat, {
|
|
287
|
+
attachmentCount: fallbackAttachments.length,
|
|
288
|
+
bundleRequested,
|
|
289
|
+
textSourceCount: textBundleSources.length,
|
|
290
|
+
textPlanShouldBundle: uploadPlan.shouldBundle,
|
|
291
|
+
});
|
|
292
|
+
if (fallbackShouldBundle) {
|
|
293
|
+
const writtenBundle = await writeBrowserBundle(sections, fallbackBundleFormat === "zip" ? allBundleSources : textBundleSources, fallbackBundleFormat);
|
|
174
294
|
fallbackAttachments.length = 0;
|
|
175
295
|
fallbackAttachments.push(writtenBundle.attachment);
|
|
176
|
-
|
|
296
|
+
if (fallbackBundleFormat === "text") {
|
|
297
|
+
fallbackAttachments.push(...rawUploadAttachments);
|
|
298
|
+
}
|
|
177
299
|
fallbackBundled = writtenBundle.metadata;
|
|
178
300
|
}
|
|
301
|
+
assertAttachmentCount(fallbackAttachments, fallbackBundleFormat);
|
|
179
302
|
fallback = {
|
|
180
303
|
composerText: fallbackComposerText,
|
|
181
304
|
attachments: fallbackAttachments,
|
|
@@ -190,7 +313,13 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
190
313
|
inlineFileCount,
|
|
191
314
|
tokenEstimateIncludesInlineFiles,
|
|
192
315
|
attachmentsPolicy,
|
|
193
|
-
attachmentMode:
|
|
316
|
+
attachmentMode: shouldBundle
|
|
317
|
+
? "bundle"
|
|
318
|
+
: attachments.length > 0
|
|
319
|
+
? "upload"
|
|
320
|
+
: selectedPlan.mode === "bundle"
|
|
321
|
+
? "inline"
|
|
322
|
+
: selectedPlan.mode,
|
|
194
323
|
fallback,
|
|
195
324
|
bundled,
|
|
196
325
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { normalizeThinkingTimeLevel } from "../oracle/thinkingTime.js";
|
|
3
4
|
import { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET } from "../browser/constants.js";
|
|
4
5
|
import { normalizeChatgptUrl } from "../browser/utils.js";
|
|
5
6
|
import { parseDuration } from "../duration.js";
|
|
@@ -63,6 +64,18 @@ export function normalizeChatGptModelForBrowser(model) {
|
|
|
63
64
|
return model;
|
|
64
65
|
}
|
|
65
66
|
export async function buildBrowserConfig(options) {
|
|
67
|
+
if (options.copyProfile && options.browserKeepBrowser) {
|
|
68
|
+
throw new Error("--copy-profile cannot be combined with --browser-keep-browser: the copied profile is a throwaway that is deleted after the run, so it must not be retained.");
|
|
69
|
+
}
|
|
70
|
+
if (options.copyProfile && options.browserManualLogin) {
|
|
71
|
+
throw new Error("--copy-profile cannot be combined with --browser-manual-login: choose either a throwaway copied profile or the persistent manual-login profile.");
|
|
72
|
+
}
|
|
73
|
+
if (options.copyProfile && options.remoteChrome) {
|
|
74
|
+
throw new Error("--copy-profile cannot be combined with --remote-chrome: copied profiles require a locally launched Chrome instance.");
|
|
75
|
+
}
|
|
76
|
+
if (options.copyProfile && options.remoteHost) {
|
|
77
|
+
throw new Error("--copy-profile cannot be combined with --remote-host: the local profile source is not available to the remote browser service.");
|
|
78
|
+
}
|
|
66
79
|
const desiredModelOverride = options.browserModelLabel?.trim();
|
|
67
80
|
const normalizedOverride = desiredModelOverride?.toLowerCase() ?? "";
|
|
68
81
|
const baseModel = options.model.toLowerCase();
|
|
@@ -97,7 +110,9 @@ export async function buildBrowserConfig(options) {
|
|
|
97
110
|
? desiredModelOverride
|
|
98
111
|
: mapModelToBrowserLabel(options.model);
|
|
99
112
|
return {
|
|
100
|
-
chromeProfile: options.
|
|
113
|
+
chromeProfile: options.copyProfile
|
|
114
|
+
? (options.browserChromeProfile ?? null)
|
|
115
|
+
: (options.browserChromeProfile ?? DEFAULT_CHROME_PROFILE),
|
|
101
116
|
chromePath: options.browserChromePath ?? null,
|
|
102
117
|
chromeCookiePath: options.browserCookiePath ?? null,
|
|
103
118
|
attachRunning,
|
|
@@ -145,6 +160,7 @@ export async function buildBrowserConfig(options) {
|
|
|
145
160
|
keepBrowser: options.browserKeepBrowser ? true : undefined,
|
|
146
161
|
manualLogin: options.browserManualLogin === undefined ? undefined : options.browserManualLogin,
|
|
147
162
|
manualLoginProfileDir: options.browserManualLoginProfileDir ?? undefined,
|
|
163
|
+
copyProfileSource: options.copyProfile ?? undefined,
|
|
148
164
|
hideWindow: options.browserHideWindow ? true : undefined,
|
|
149
165
|
desiredModel,
|
|
150
166
|
modelStrategy,
|
|
@@ -153,7 +169,7 @@ export async function buildBrowserConfig(options) {
|
|
|
153
169
|
allowCookieErrors: options.browserAllowCookieErrors ?? true,
|
|
154
170
|
remoteChrome,
|
|
155
171
|
browserTabRef: options.browserTab ?? undefined,
|
|
156
|
-
thinkingTime: options.browserThinkingTime,
|
|
172
|
+
thinkingTime: normalizeThinkingTimeLevel(options.browserThinkingTime) ?? undefined,
|
|
157
173
|
researchMode: options.browserResearch === "deep" ? "deep" : "off",
|
|
158
174
|
archiveConversations: options.browserArchive,
|
|
159
175
|
};
|
|
@@ -170,6 +186,7 @@ function validateAttachRunningOptions(options, { attachRunning, hasInlineCookies
|
|
|
170
186
|
options.browserKeepBrowser ? "--browser-keep-browser" : null,
|
|
171
187
|
options.browserManualLogin ? "--browser-manual-login" : null,
|
|
172
188
|
options.browserManualLoginProfileDir ? "--browser-manual-login-profile-dir" : null,
|
|
189
|
+
options.copyProfile ? "--copy-profile" : null,
|
|
173
190
|
hasInlineCookies ? "--browser-inline-cookies/--browser-inline-cookies-file" : null,
|
|
174
191
|
options.browserPort != null || options.browserDebugPort != null
|
|
175
192
|
? "--browser-port/--browser-debug-port"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CHATGPT_URL } from "../browser/constants.js";
|
|
2
2
|
import { normalizeChatgptUrl } from "../browser/utils.js";
|
|
3
|
+
import { normalizeThinkingTimeLevel } from "../oracle/thinkingTime.js";
|
|
3
4
|
export function applyBrowserDefaultsFromConfig(options, config, getSource) {
|
|
4
5
|
const browser = config.browser;
|
|
5
6
|
if (!browser)
|
|
@@ -89,7 +90,7 @@ export function applyBrowserDefaultsFromConfig(options, config, getSource) {
|
|
|
89
90
|
options.browserModelStrategy = browser.modelStrategy;
|
|
90
91
|
}
|
|
91
92
|
if (isUnset("browserThinkingTime") && browser.thinkingTime !== undefined) {
|
|
92
|
-
options.browserThinkingTime = browser.thinkingTime;
|
|
93
|
+
options.browserThinkingTime = normalizeThinkingTimeLevel(browser.thinkingTime) ?? undefined;
|
|
93
94
|
}
|
|
94
95
|
if (isUnset("browserResearch") && browser.researchMode !== undefined) {
|
|
95
96
|
options.browserResearch = browser.researchMode;
|
package/dist/src/cli/options.js
CHANGED
|
@@ -3,6 +3,7 @@ import { parseDuration } from "../duration.js";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fg from "fast-glob";
|
|
5
5
|
import { DEFAULT_MODEL, MODEL_CONFIGS } from "../oracle/config.js";
|
|
6
|
+
import { normalizeThinkingTimeLevel } from "../oracle/thinkingTime.js";
|
|
6
7
|
export function collectPaths(value, previous = []) {
|
|
7
8
|
if (!value) {
|
|
8
9
|
return previous;
|
|
@@ -127,6 +128,13 @@ export function parseSearchOption(value) {
|
|
|
127
128
|
}
|
|
128
129
|
throw new InvalidArgumentError('Search mode must be "on" or "off".');
|
|
129
130
|
}
|
|
131
|
+
export function parseThinkingTimeOption(value) {
|
|
132
|
+
const normalized = normalizeThinkingTimeLevel(value);
|
|
133
|
+
if (normalized) {
|
|
134
|
+
return normalized;
|
|
135
|
+
}
|
|
136
|
+
throw new InvalidArgumentError('Thinking time must be one of "light", "standard", "extended", "heavy", or a ChatGPT UI alias like "instant", "medium", "high", or "extra-high".');
|
|
137
|
+
}
|
|
130
138
|
export function normalizeModelOption(value) {
|
|
131
139
|
return (value ?? "").trim();
|
|
132
140
|
}
|
|
@@ -389,6 +389,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
389
389
|
userError.details?.stage === "assistant-timeout";
|
|
390
390
|
const cloudflareChallenge = userError?.category === "browser-automation" &&
|
|
391
391
|
userError.details?.stage === "cloudflare-challenge";
|
|
392
|
+
const browserCanReattach = !browserConfig?.copyProfileSource;
|
|
392
393
|
let reattachGuidanceLogged = false;
|
|
393
394
|
const logBrowserReattachGuidance = (runtime) => {
|
|
394
395
|
if (reattachGuidanceLogged || mode !== "browser")
|
|
@@ -399,7 +400,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
399
400
|
reattachGuidanceLogged = true;
|
|
400
401
|
log(formatBrowserReattachGuidance(sessionMeta.id));
|
|
401
402
|
};
|
|
402
|
-
if (connectionLost && mode === "browser") {
|
|
403
|
+
if (connectionLost && mode === "browser" && browserCanReattach) {
|
|
403
404
|
const runtime = userError.details
|
|
404
405
|
?.runtime;
|
|
405
406
|
const recoverableRuntime = runtime ?? sessionMeta.browser?.runtime;
|
|
@@ -456,7 +457,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
456
457
|
logBrowserReattachGuidance(recoverableRuntime);
|
|
457
458
|
return;
|
|
458
459
|
}
|
|
459
|
-
if (assistantTimeout && mode === "browser") {
|
|
460
|
+
if (assistantTimeout && mode === "browser" && browserCanReattach) {
|
|
460
461
|
const runtime = userError.details
|
|
461
462
|
?.runtime;
|
|
462
463
|
log(dim("Assistant response timed out; marking capture incomplete for reattach."));
|
|
@@ -509,9 +510,14 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
509
510
|
}
|
|
510
511
|
if (cloudflareChallenge && mode === "browser") {
|
|
511
512
|
const details = userError.details;
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
513
|
+
if (browserCanReattach) {
|
|
514
|
+
log(dim("Cloudflare challenge detected; browser left running so you can complete the check."));
|
|
515
|
+
if (details?.reuseProfileHint) {
|
|
516
|
+
log(dim(`Reuse this browser profile with: ${details.reuseProfileHint}`));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
log(dim("Cloudflare challenge detected; copied profile closed and removed."));
|
|
515
521
|
}
|
|
516
522
|
}
|
|
517
523
|
if (userError) {
|
|
@@ -527,10 +533,10 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
527
533
|
if (transportLine) {
|
|
528
534
|
log(dim(`Transport: ${transportLine}`));
|
|
529
535
|
}
|
|
530
|
-
const browserRuntime = mode === "browser"
|
|
536
|
+
const browserRuntime = mode === "browser" && browserCanReattach
|
|
531
537
|
? userError?.details?.runtime
|
|
532
538
|
: undefined;
|
|
533
|
-
if (!cloudflareChallenge) {
|
|
539
|
+
if (!cloudflareChallenge && browserCanReattach) {
|
|
534
540
|
logBrowserReattachGuidance(browserRuntime ?? sessionMeta.browser?.runtime);
|
|
535
541
|
}
|
|
536
542
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { getOracleHomeDir } from "../../oracleHome.js";
|
|
5
|
+
import { browserThinkingTimeInputSchema, browserThinkingTimeRawSchema, } from "../types.js";
|
|
5
6
|
import { consultOutputShape, runConsultTool } from "./consult.js";
|
|
6
7
|
const chatGptImageInputShape = {
|
|
7
8
|
prompt: z.string().min(1, "Prompt is required.").describe("Image generation prompt."),
|
|
@@ -29,8 +30,7 @@ const chatGptImageInputShape = {
|
|
|
29
30
|
.enum(["auto", "never", "always"])
|
|
30
31
|
.optional()
|
|
31
32
|
.describe('How to deliver files. Defaults to "always" when files are present so reference images are uploaded.'),
|
|
32
|
-
browserThinkingTime:
|
|
33
|
-
.enum(["light", "standard", "extended", "heavy"])
|
|
33
|
+
browserThinkingTime: browserThinkingTimeRawSchema
|
|
34
34
|
.optional()
|
|
35
35
|
.describe("Set ChatGPT thinking time when supported by the chosen model."),
|
|
36
36
|
browserModelStrategy: z
|
|
@@ -58,7 +58,12 @@ const chatGptImageOutputShape = {
|
|
|
58
58
|
...consultOutputShape,
|
|
59
59
|
requestedOutputPath: z.string(),
|
|
60
60
|
};
|
|
61
|
-
const chatGptImageInputSchema = z
|
|
61
|
+
const chatGptImageInputSchema = z
|
|
62
|
+
.object({
|
|
63
|
+
...chatGptImageInputShape,
|
|
64
|
+
browserThinkingTime: browserThinkingTimeInputSchema.optional(),
|
|
65
|
+
})
|
|
66
|
+
.strict();
|
|
62
67
|
function resolveDefaultImageOutputPath() {
|
|
63
68
|
// Include a random token so concurrent agent calls in the same millisecond do
|
|
64
69
|
// not resolve to the same default path and overwrite each other.
|
|
@@ -20,11 +20,12 @@ async function readSessionLogTail(sessionId, maxBytes) {
|
|
|
20
20
|
import { performSessionRun } from "../../cli/sessionRunner.js";
|
|
21
21
|
import { runDryRunSummary } from "../../cli/dryRun.js";
|
|
22
22
|
import { CHATGPT_URL } from "../../browser/constants.js";
|
|
23
|
-
import { CONSULT_PRESETS, consultInputSchema } from "../types.js";
|
|
23
|
+
import { CONSULT_PRESETS, browserThinkingTimeRawSchema, consultInputSchema } from "../types.js";
|
|
24
24
|
import { applyConsultPreset } from "../consultPresets.js";
|
|
25
25
|
import { loadUserConfig } from "../../config.js";
|
|
26
26
|
import { resolveNotificationSettings } from "../../cli/notifier.js";
|
|
27
27
|
import { mapModelToBrowserLabel, resolveBrowserModelLabel } from "../../cli/browserConfig.js";
|
|
28
|
+
import { normalizeThinkingTimeLevel } from "../../oracle/thinkingTime.js";
|
|
28
29
|
// Use raw shapes so the MCP SDK (with its bundled Zod) wraps them and emits valid JSON Schema.
|
|
29
30
|
const consultInputShape = {
|
|
30
31
|
preset: z
|
|
@@ -61,11 +62,10 @@ const consultInputShape = {
|
|
|
61
62
|
.optional()
|
|
62
63
|
.describe("Browser-only: bundle many files into a single upload (helps with upload limits)."),
|
|
63
64
|
browserBundleFormat: z
|
|
64
|
-
.enum(["text", "zip"])
|
|
65
|
+
.enum(["auto", "text", "zip"])
|
|
65
66
|
.optional()
|
|
66
|
-
.describe('Browser-only: bundle upload format when browserBundleFiles is true or auto-bundling is needed. Defaults to "
|
|
67
|
-
browserThinkingTime:
|
|
68
|
-
.enum(["light", "standard", "extended", "heavy"])
|
|
67
|
+
.describe('Browser-only: bundle upload format when browserBundleFiles is true or auto-bundling is needed. Defaults to "auto"; "auto" uses ZIP when bundled inputs include raw/binary files.'),
|
|
68
|
+
browserThinkingTime: browserThinkingTimeRawSchema
|
|
69
69
|
.optional()
|
|
70
70
|
.describe("Browser-only: set ChatGPT thinking time when supported by the chosen model."),
|
|
71
71
|
browserModelStrategy: z
|
|
@@ -166,7 +166,7 @@ const consultDryRunResolvedShape = z.object({
|
|
|
166
166
|
researchMode: z.string().nullable().optional(),
|
|
167
167
|
attachments: z.string().optional(),
|
|
168
168
|
bundleFiles: z.boolean().optional(),
|
|
169
|
-
bundleFormat: z.enum(["text", "zip"]).optional(),
|
|
169
|
+
bundleFormat: z.enum(["auto", "text", "zip"]).optional(),
|
|
170
170
|
keepBrowser: z.boolean().optional(),
|
|
171
171
|
manualLogin: z.boolean().optional(),
|
|
172
172
|
profileDir: z.string().nullable().optional(),
|
|
@@ -266,6 +266,7 @@ export function buildConsultBrowserConfig({ userConfig, env, runModel, inputMode
|
|
|
266
266
|
const manualLogin = hasProfileDir
|
|
267
267
|
? true
|
|
268
268
|
: (configuredBrowser.manualLogin ?? process.platform === "win32");
|
|
269
|
+
const configuredThinkingTime = normalizeThinkingTimeLevel(configuredBrowser.thinkingTime);
|
|
269
270
|
return {
|
|
270
271
|
...configuredBrowser,
|
|
271
272
|
url: configuredUrl,
|
|
@@ -278,7 +279,7 @@ export function buildConsultBrowserConfig({ userConfig, env, runModel, inputMode
|
|
|
278
279
|
manualLoginProfileDir: manualLogin
|
|
279
280
|
? ((envProfileDir || configuredBrowser.manualLoginProfileDir) ?? null)
|
|
280
281
|
: null,
|
|
281
|
-
thinkingTime: browserThinkingTime ??
|
|
282
|
+
thinkingTime: browserThinkingTime ?? configuredThinkingTime ?? undefined,
|
|
282
283
|
modelStrategy: browserModelStrategy ?? configuredBrowser.modelStrategy,
|
|
283
284
|
researchMode: browserResearchMode ?? configuredBrowser.researchMode,
|
|
284
285
|
archiveConversations: browserArchive ?? configuredBrowser.archiveConversations,
|
|
@@ -358,7 +359,7 @@ export function formatConsultDryRunResolved(details) {
|
|
|
358
359
|
lines.push(` browser research mode: ${details.browser.researchMode ?? "off"}`);
|
|
359
360
|
lines.push(` browser attachments: ${details.browser.attachments ?? "auto"}`);
|
|
360
361
|
lines.push(` browser bundle files: ${details.browser.bundleFiles ? "yes" : "no"}`);
|
|
361
|
-
lines.push(` browser bundle format: ${details.browser.bundleFormat ?? "
|
|
362
|
+
lines.push(` browser bundle format: ${details.browser.bundleFormat ?? "auto"}`);
|
|
362
363
|
lines.push(` browser keep browser: ${details.browser.keepBrowser ? "yes" : "no"}`);
|
|
363
364
|
lines.push(` browser manual login: ${details.browser.manualLogin ? "yes" : "no"}`);
|
|
364
365
|
if (details.browser.profileDir) {
|
package/dist/src/mcp/types.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { THINKING_TIME_INPUT_VALUES, normalizeThinkingTimeLevel } from "../oracle/thinkingTime.js";
|
|
2
3
|
export const CONSULT_PRESETS = ["chatgpt-pro-heavy"];
|
|
4
|
+
export const browserThinkingTimeRawSchema = z.enum(THINKING_TIME_INPUT_VALUES);
|
|
5
|
+
export const browserThinkingTimeInputSchema = browserThinkingTimeRawSchema.transform((value) => {
|
|
6
|
+
const normalized = normalizeThinkingTimeLevel(value);
|
|
7
|
+
if (!normalized) {
|
|
8
|
+
throw new Error(`Unknown browserThinkingTime value: ${value}`);
|
|
9
|
+
}
|
|
10
|
+
return normalized;
|
|
11
|
+
});
|
|
3
12
|
export const consultInputSchema = z
|
|
4
13
|
.object({
|
|
5
14
|
preset: z.enum(CONSULT_PRESETS).optional(),
|
|
@@ -11,8 +20,8 @@ export const consultInputSchema = z
|
|
|
11
20
|
browserModelLabel: z.string().optional(),
|
|
12
21
|
browserAttachments: z.enum(["auto", "never", "always"]).optional(),
|
|
13
22
|
browserBundleFiles: z.boolean().optional(),
|
|
14
|
-
browserBundleFormat: z.enum(["text", "zip"]).optional(),
|
|
15
|
-
browserThinkingTime:
|
|
23
|
+
browserBundleFormat: z.enum(["auto", "text", "zip"]).optional(),
|
|
24
|
+
browserThinkingTime: browserThinkingTimeInputSchema.optional(),
|
|
16
25
|
browserModelStrategy: z.enum(["select", "current", "ignore"]).optional(),
|
|
17
26
|
browserResearchMode: z.enum(["deep"]).optional(),
|
|
18
27
|
browserArchive: z.enum(["auto", "always", "never"]).optional(),
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const THINKING_TIME_LEVELS = ["light", "standard", "extended", "heavy"];
|
|
2
|
+
export const THINKING_TIME_ALIASES = [
|
|
3
|
+
"instant",
|
|
4
|
+
"low",
|
|
5
|
+
"medium",
|
|
6
|
+
"high",
|
|
7
|
+
"extra-high",
|
|
8
|
+
"extra high",
|
|
9
|
+
"extrahigh",
|
|
10
|
+
"xhigh",
|
|
11
|
+
];
|
|
12
|
+
export const THINKING_TIME_INPUT_VALUES = [
|
|
13
|
+
...THINKING_TIME_LEVELS,
|
|
14
|
+
...THINKING_TIME_ALIASES,
|
|
15
|
+
];
|
|
16
|
+
export function normalizeThinkingTimeLevel(value) {
|
|
17
|
+
const normalized = (value ?? "")
|
|
18
|
+
.trim()
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.replace(/[_\s]+/g, "-");
|
|
21
|
+
switch (normalized) {
|
|
22
|
+
case "light":
|
|
23
|
+
case "instant":
|
|
24
|
+
case "low":
|
|
25
|
+
return "light";
|
|
26
|
+
case "standard":
|
|
27
|
+
case "medium":
|
|
28
|
+
return "standard";
|
|
29
|
+
case "extended":
|
|
30
|
+
case "high":
|
|
31
|
+
return "extended";
|
|
32
|
+
case "heavy":
|
|
33
|
+
case "extra-high":
|
|
34
|
+
case "extrahigh":
|
|
35
|
+
case "xhigh":
|
|
36
|
+
return "heavy";
|
|
37
|
+
default:
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
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.15.0",
|
|
4
4
|
"description": "CLI wrapper around OpenAI Responses API with 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.8.0",
|
|
62
62
|
"@google/generative-ai": "^0.24.1",
|
|
63
63
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
64
|
-
"@steipete/sweet-cookie": "^0.
|
|
64
|
+
"@steipete/sweet-cookie": "^0.4.0",
|
|
65
65
|
"chalk": "^5.6.2",
|
|
66
66
|
"chrome-launcher": "^1.2.1",
|
|
67
67
|
"chrome-remote-interface": "^0.34.0",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"inquirer": "14.0.2",
|
|
74
74
|
"json5": "^2.2.3",
|
|
75
75
|
"kleur": "^4.1.5",
|
|
76
|
-
"markdansi": "0.
|
|
76
|
+
"markdansi": "0.3.1",
|
|
77
77
|
"openai": "^6.42.0",
|
|
78
78
|
"osc-progress": "^0.3.0",
|
|
79
79
|
"qs": "^6.15.2",
|
|
@@ -87,8 +87,8 @@
|
|
|
87
87
|
"@types/chrome-remote-interface": "^0.34.0",
|
|
88
88
|
"@types/inquirer": "^9.0.9",
|
|
89
89
|
"@types/node": "^25.9.2",
|
|
90
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
91
|
-
"@vitest/coverage-v8": "4.1.
|
|
90
|
+
"@typescript/native-preview": "7.0.0-dev.20260612.1",
|
|
91
|
+
"@vitest/coverage-v8": "4.1.9",
|
|
92
92
|
"devtools-protocol": "0.0.1643702",
|
|
93
93
|
"es-toolkit": "^1.47.0",
|
|
94
94
|
"esbuild": "^0.28.0",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"puppeteer-core": "^25.1.0",
|
|
98
98
|
"tsx": "^4.22.4",
|
|
99
99
|
"typescript": "^6.0.3",
|
|
100
|
-
"vitest": "^4.1.
|
|
100
|
+
"vitest": "^4.1.9"
|
|
101
101
|
},
|
|
102
102
|
"devEngines": {
|
|
103
103
|
"runtime": [
|
|
Binary file
|
|
Binary file
|