@steipete/oracle 0.18.0 → 0.19.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 +1 -1
- package/dist/bin/oracle-cli.js +65 -63
- package/dist/bin/oracle-mcp.js +0 -0
- package/dist/src/browser/actions/archiveConversation.js +31 -7
- package/dist/src/browser/actions/assistantResponse.js +43 -1
- package/dist/src/browser/actions/attachmentContext.js +239 -0
- package/dist/src/browser/actions/attachmentDataTransfer.js +19 -1
- package/dist/src/browser/actions/attachmentEvidence.js +78 -0
- package/dist/src/browser/actions/attachmentProgress.js +41 -0
- package/dist/src/browser/actions/attachmentPrompt.js +130 -0
- package/dist/src/browser/actions/attachments.js +306 -355
- package/dist/src/browser/actions/deepResearch.js +2 -1
- package/dist/src/browser/actions/modelSelection.js +13 -3
- package/dist/src/browser/actions/navigation.js +1 -1
- package/dist/src/browser/actions/promptComposer.js +361 -186
- package/dist/src/browser/actions/remoteFileTransfer.js +4 -2
- package/dist/src/browser/actions/thinkingStatus.js +2 -5
- package/dist/src/browser/actions/thinkingTime.js +119 -16
- package/dist/src/browser/attachRunning.js +46 -2
- package/dist/src/browser/attachmentValidation.js +53 -0
- package/dist/src/browser/cancellation.js +105 -0
- package/dist/src/browser/chatgptImages.js +2 -0
- package/dist/src/browser/chatgptThrottle.js +104 -0
- package/dist/src/browser/chromeLifecycle.js +267 -43
- package/dist/src/browser/config.js +13 -0
- package/dist/src/browser/detect.js +1 -1
- package/dist/src/browser/index.js +444 -261
- package/dist/src/browser/modelDisplay.js +9 -0
- package/dist/src/browser/pageActions.js +2 -2
- package/dist/src/browser/profileState.js +114 -9
- package/dist/src/browser/projectSourcesRunner.js +83 -15
- package/dist/src/browser/prompt.js +272 -113
- package/dist/src/browser/providers/chatgptDomProvider.js +2 -0
- package/dist/src/browser/reattach.js +10 -3
- package/dist/src/browser/sessionRunner.js +71 -11
- package/dist/src/browser/tabLeaseRegistry.js +414 -38
- package/dist/src/browser/utils.js +20 -2
- package/dist/src/cli/browserConfig.js +3 -1
- package/dist/src/cli/browserDefaults.js +10 -0
- package/dist/src/cli/browserTabs.js +12 -29
- package/dist/src/cli/detachedSession.js +69 -0
- package/dist/src/cli/dryRun.js +58 -48
- package/dist/src/cli/harvestIntegrity.js +140 -0
- package/dist/src/cli/serveBrowserConfig.js +24 -0
- package/dist/src/cli/sessionDisplay.js +9 -2
- package/dist/src/cli/sessionRunner.js +91 -7
- package/dist/src/config.js +1 -0
- package/dist/src/gemini-web/client.js +67 -21
- package/dist/src/gemini-web/executor.js +4 -0
- package/dist/src/gemini-web/http.js +15 -0
- package/dist/src/gemini-web/models.js +15 -11
- package/dist/src/mcp/server.js +11 -14
- package/dist/src/mcp/tools/chatgptImage.js +4 -4
- package/dist/src/mcp/tools/consult.js +84 -22
- package/dist/src/mcp/tools/projectSources.js +4 -6
- package/dist/src/mcp/tools/sessionResources.js +1 -1
- package/dist/src/mcp/tools/sessions.js +2 -2
- package/dist/src/mcp/tools/wait.js +182 -0
- package/dist/src/mcp/types.js +7 -0
- package/dist/src/oracle/errors.js +11 -0
- package/dist/src/oracle/providerRoutePlan.js +3 -2
- package/dist/src/remote/client.js +70 -6
- package/dist/src/remote/health.js +6 -2
- package/dist/src/remote/runSlots.js +83 -0
- package/dist/src/remote/server.js +345 -71
- package/dist/src/sessionManager.js +36 -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 +25 -23
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/bin/oracle.js +0 -683
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +0 -1
- package/dist/docs-site/RELEASING.html +0 -410
- package/dist/docs-site/agents.html +0 -374
- package/dist/docs-site/anthropic.html +0 -368
- package/dist/docs-site/bridge.html +0 -400
- package/dist/docs-site/browser-mode.html +0 -588
- package/dist/docs-site/chromium-forks.html +0 -347
- package/dist/docs-site/cli-reference.html +0 -346
- package/dist/docs-site/configuration.html +0 -436
- package/dist/docs-site/favicon.svg +0 -14
- package/dist/docs-site/followup.html +0 -368
- package/dist/docs-site/gemini.html +0 -379
- package/dist/docs-site/grok.html +0 -325
- package/dist/docs-site/index.html +0 -360
- package/dist/docs-site/install.html +0 -335
- package/dist/docs-site/linux.html +0 -321
- package/dist/docs-site/llms.txt +0 -43
- package/dist/docs-site/manual-tests.html +0 -595
- package/dist/docs-site/mcp.html +0 -368
- package/dist/docs-site/multimodel.html +0 -364
- package/dist/docs-site/mythical-pro-agents.html +0 -360
- package/dist/docs-site/notifier.html +0 -338
- package/dist/docs-site/openai-endpoints.html +0 -387
- package/dist/docs-site/openrouter.html +0 -344
- package/dist/docs-site/quickstart.html +0 -369
- package/dist/docs-site/refactor/ux.html +0 -532
- package/dist/docs-site/sessions.html +0 -388
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +0 -79
- package/dist/docs-site/spec.html +0 -363
- package/dist/docs-site/testing.html +0 -320
- package/dist/docs-site/tui-debug.html +0 -326
- package/dist/docs-site/windows-work.html +0 -323
- package/dist/docs-site/windows.html +0 -320
- package/dist/markdansi/types/index.js +0 -4
- package/dist/oracle/bin/oracle-cli.js +0 -472
- package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
- package/dist/oracle/src/browser/actions/attachments.js +0 -82
- package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
- package/dist/oracle/src/browser/actions/navigation.js +0 -75
- package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
- package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
- package/dist/oracle/src/browser/config.js +0 -33
- package/dist/oracle/src/browser/constants.js +0 -40
- package/dist/oracle/src/browser/cookies.js +0 -210
- package/dist/oracle/src/browser/domDebug.js +0 -36
- package/dist/oracle/src/browser/index.js +0 -331
- package/dist/oracle/src/browser/pageActions.js +0 -5
- package/dist/oracle/src/browser/prompt.js +0 -88
- package/dist/oracle/src/browser/promptSummary.js +0 -20
- package/dist/oracle/src/browser/sessionRunner.js +0 -80
- package/dist/oracle/src/browser/types.js +0 -1
- package/dist/oracle/src/browser/utils.js +0 -62
- package/dist/oracle/src/browserMode.js +0 -1
- package/dist/oracle/src/cli/browserConfig.js +0 -44
- package/dist/oracle/src/cli/dryRun.js +0 -59
- package/dist/oracle/src/cli/engine.js +0 -17
- package/dist/oracle/src/cli/errorUtils.js +0 -9
- package/dist/oracle/src/cli/help.js +0 -70
- package/dist/oracle/src/cli/markdownRenderer.js +0 -15
- package/dist/oracle/src/cli/options.js +0 -103
- package/dist/oracle/src/cli/promptRequirement.js +0 -14
- package/dist/oracle/src/cli/rootAlias.js +0 -30
- package/dist/oracle/src/cli/sessionCommand.js +0 -77
- package/dist/oracle/src/cli/sessionDisplay.js +0 -270
- package/dist/oracle/src/cli/sessionRunner.js +0 -94
- package/dist/oracle/src/heartbeat.js +0 -43
- package/dist/oracle/src/oracle/client.js +0 -48
- package/dist/oracle/src/oracle/config.js +0 -29
- package/dist/oracle/src/oracle/errors.js +0 -101
- package/dist/oracle/src/oracle/files.js +0 -220
- package/dist/oracle/src/oracle/format.js +0 -33
- package/dist/oracle/src/oracle/fsAdapter.js +0 -7
- package/dist/oracle/src/oracle/oscProgress.js +0 -60
- package/dist/oracle/src/oracle/request.js +0 -48
- package/dist/oracle/src/oracle/run.js +0 -444
- package/dist/oracle/src/oracle/tokenStats.js +0 -39
- package/dist/oracle/src/oracle/types.js +0 -1
- package/dist/oracle/src/oracle.js +0 -9
- package/dist/oracle/src/sessionManager.js +0 -205
- package/dist/oracle/src/version.js +0 -39
- package/dist/scripts/chrome/browser-tools.js +0 -295
- package/dist/src/browser/chromeCookies.js +0 -312
- package/dist/src/browser/keytarShim.js +0 -56
- package/dist/src/browser/profileSync.js +0 -141
- package/dist/src/browser/windowsCookies.js +0 -219
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
|
|
4
|
+
import { fetchGeminiWebResource } from "./http.js";
|
|
5
|
+
import { buildGeminiWebModelHeader, FALLBACK_GEMINI_WEB_MODEL, getGeminiWebModelSelection, } from "./models.js";
|
|
6
|
+
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36";
|
|
5
7
|
const MODEL_HEADER_NAME = "x-goog-ext-525001261-jspb";
|
|
6
8
|
const GEMINI_APP_URL = "https://gemini.google.com/app";
|
|
7
9
|
const GEMINI_STREAM_GENERATE_URL = "https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate";
|
|
@@ -61,7 +63,7 @@ function buildCookieHeader(cookieMap) {
|
|
|
61
63
|
}
|
|
62
64
|
export async function fetchGeminiAccessToken(cookieMap, signal) {
|
|
63
65
|
const cookieHeader = buildCookieHeader(cookieMap);
|
|
64
|
-
const res = await
|
|
66
|
+
const res = await fetchGeminiWebResource(GEMINI_APP_URL, {
|
|
65
67
|
redirect: "follow",
|
|
66
68
|
signal,
|
|
67
69
|
headers: {
|
|
@@ -112,7 +114,7 @@ function ensureFullSizeImageUrl(url) {
|
|
|
112
114
|
async function fetchWithCookiePreservingRedirects(url, init, signal, maxRedirects = 10) {
|
|
113
115
|
let current = url;
|
|
114
116
|
for (let i = 0; i <= maxRedirects; i += 1) {
|
|
115
|
-
const res = await
|
|
117
|
+
const res = await fetchGeminiWebResource(current, { ...init, redirect: "manual", signal });
|
|
116
118
|
if (res.status >= 300 && res.status < 400) {
|
|
117
119
|
const location = res.headers.get("location");
|
|
118
120
|
if (!location)
|
|
@@ -146,7 +148,7 @@ async function uploadGeminiFile(filePath, signal) {
|
|
|
146
148
|
const mimeType = resolveGeminiUploadMimeType(absPath);
|
|
147
149
|
const form = new FormData();
|
|
148
150
|
form.append("file", new Blob([data], { type: mimeType }), fileName);
|
|
149
|
-
const res = await
|
|
151
|
+
const res = await fetchGeminiWebResource(GEMINI_UPLOAD_URL, {
|
|
150
152
|
method: "POST",
|
|
151
153
|
redirect: "follow",
|
|
152
154
|
signal,
|
|
@@ -162,18 +164,38 @@ async function uploadGeminiFile(filePath, signal) {
|
|
|
162
164
|
}
|
|
163
165
|
return { id: text, name: fileName, mimeType };
|
|
164
166
|
}
|
|
165
|
-
function buildGeminiFReqPayload(prompt, uploaded, chatMetadata) {
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
167
|
+
function buildGeminiFReqPayload(prompt, uploaded, chatMetadata, model, requestId) {
|
|
168
|
+
const request = Array(97).fill(null);
|
|
169
|
+
request[0] = [
|
|
170
|
+
prompt,
|
|
171
|
+
0,
|
|
172
|
+
null,
|
|
173
|
+
uploaded.map((file) => [[file.id, 1, null, file.mimeType], file.name]),
|
|
174
|
+
null,
|
|
175
|
+
null,
|
|
176
|
+
0,
|
|
177
|
+
];
|
|
178
|
+
request[1] = ["en"];
|
|
179
|
+
request[2] = chatMetadata ?? ["", "", "", null, null, null, null, null, null, ""];
|
|
180
|
+
request[6] = [1];
|
|
181
|
+
request[7] = 1;
|
|
182
|
+
request[10] = 1;
|
|
183
|
+
request[11] = 0;
|
|
184
|
+
request[17] = [[0]];
|
|
185
|
+
request[18] = 0;
|
|
186
|
+
request[27] = 1;
|
|
187
|
+
request[30] = [4];
|
|
188
|
+
request[41] = [1];
|
|
189
|
+
request[53] = 0;
|
|
190
|
+
request[59] = requestId;
|
|
191
|
+
request[61] = [];
|
|
192
|
+
request[68] = 2;
|
|
193
|
+
const selection = getGeminiWebModelSelection(model);
|
|
194
|
+
request[79] = selection.modelCode;
|
|
195
|
+
request[80] = selection.thinkingCode;
|
|
196
|
+
request[91] = 0;
|
|
197
|
+
request[96] = chatMetadata == null ? 1 : 0;
|
|
198
|
+
return JSON.stringify([null, JSON.stringify(request)]);
|
|
177
199
|
}
|
|
178
200
|
export function parseGeminiStreamGenerateResponse(rawText) {
|
|
179
201
|
const responseJson = JSON.parse(trimGeminiJsonEnvelope(rawText));
|
|
@@ -274,11 +296,12 @@ export async function runGeminiWebOnce(input) {
|
|
|
274
296
|
}
|
|
275
297
|
uploaded.push(await uploadGeminiFile(file, input.signal));
|
|
276
298
|
}
|
|
277
|
-
const
|
|
299
|
+
const requestId = randomUUID().toUpperCase();
|
|
300
|
+
const fReq = buildGeminiFReqPayload(input.prompt, uploaded, input.chatMetadata ?? null, input.model, requestId);
|
|
278
301
|
const params = new URLSearchParams();
|
|
279
302
|
params.set("at", at);
|
|
280
303
|
params.set("f.req", fReq);
|
|
281
|
-
const res = await
|
|
304
|
+
const res = await fetchGeminiWebResource(GEMINI_STREAM_GENERATE_URL, {
|
|
282
305
|
method: "POST",
|
|
283
306
|
redirect: "follow",
|
|
284
307
|
signal: input.signal,
|
|
@@ -290,6 +313,9 @@ export async function runGeminiWebOnce(input) {
|
|
|
290
313
|
"user-agent": USER_AGENT,
|
|
291
314
|
cookie: cookieHeader,
|
|
292
315
|
[MODEL_HEADER_NAME]: buildGeminiWebModelHeader(input.model),
|
|
316
|
+
"x-goog-ext-73010989-jspb": "[0]",
|
|
317
|
+
"x-goog-ext-73010990-jspb": "[0]",
|
|
318
|
+
"x-goog-ext-525005358-jspb": JSON.stringify([requestId, 1]),
|
|
293
319
|
},
|
|
294
320
|
body: params.toString(),
|
|
295
321
|
});
|
|
@@ -338,10 +364,30 @@ export async function runGeminiWebOnce(input) {
|
|
|
338
364
|
export async function runGeminiWebWithFallback(input) {
|
|
339
365
|
const attempt = await runGeminiWebOnce(input);
|
|
340
366
|
if (isGeminiModelUnavailable(attempt.errorCode) && input.model !== FALLBACK_GEMINI_WEB_MODEL) {
|
|
367
|
+
if (input.allowModelFallback === false) {
|
|
368
|
+
throw new Error(`Requested Gemini web model ${input.model} is unavailable and model fallback is disabled.`);
|
|
369
|
+
}
|
|
341
370
|
const fallback = await runGeminiWebOnce({ ...input, model: FALLBACK_GEMINI_WEB_MODEL });
|
|
342
|
-
return {
|
|
371
|
+
return {
|
|
372
|
+
...assertGeminiWebRunSucceeded(fallback),
|
|
373
|
+
effectiveModel: FALLBACK_GEMINI_WEB_MODEL,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
return { ...assertGeminiWebRunSucceeded(attempt), effectiveModel: input.model };
|
|
377
|
+
}
|
|
378
|
+
function assertGeminiWebRunSucceeded(output) {
|
|
379
|
+
if (output.errorCode !== undefined) {
|
|
380
|
+
throw new Error(`Gemini web request failed with error code ${output.errorCode}.`);
|
|
381
|
+
}
|
|
382
|
+
if (output.errorMessage) {
|
|
383
|
+
throw new Error(`Gemini web request failed: ${output.errorMessage}`);
|
|
384
|
+
}
|
|
385
|
+
if (!output.text.trim() &&
|
|
386
|
+
output.images.length === 0 &&
|
|
387
|
+
extractGgdlUrls(output.rawResponseText).length === 0) {
|
|
388
|
+
throw new Error("Gemini web request ended without a response.");
|
|
343
389
|
}
|
|
344
|
-
return
|
|
390
|
+
return output;
|
|
345
391
|
}
|
|
346
392
|
export async function saveFirstGeminiImageFromOutput(output, cookieMap, outputPath, signal) {
|
|
347
393
|
const generatedOrWeb = output.images.find((img) => img.kind === "generated") ?? output.images[0];
|
|
@@ -325,6 +325,7 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
325
325
|
cookieMap: cookieResult.cookieMap,
|
|
326
326
|
chatMetadata: null,
|
|
327
327
|
signal: controller.signal,
|
|
328
|
+
allowModelFallback: geminiOptions.allowModelFallback,
|
|
328
329
|
});
|
|
329
330
|
const editPrompt = `Use image generation tool to ${prompt}`;
|
|
330
331
|
const out = await runGeminiWebWithFallback({
|
|
@@ -334,6 +335,7 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
334
335
|
cookieMap: cookieResult.cookieMap,
|
|
335
336
|
chatMetadata: intro.metadata,
|
|
336
337
|
signal: controller.signal,
|
|
338
|
+
allowModelFallback: geminiOptions.allowModelFallback,
|
|
337
339
|
});
|
|
338
340
|
response = {
|
|
339
341
|
text: out.text ?? null,
|
|
@@ -357,6 +359,7 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
357
359
|
cookieMap: cookieResult.cookieMap,
|
|
358
360
|
chatMetadata: null,
|
|
359
361
|
signal: controller.signal,
|
|
362
|
+
allowModelFallback: geminiOptions.allowModelFallback,
|
|
360
363
|
});
|
|
361
364
|
response = {
|
|
362
365
|
text: out.text ?? null,
|
|
@@ -379,6 +382,7 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
379
382
|
cookieMap: cookieResult.cookieMap,
|
|
380
383
|
chatMetadata: null,
|
|
381
384
|
signal: controller.signal,
|
|
385
|
+
allowModelFallback: geminiOptions.allowModelFallback,
|
|
382
386
|
});
|
|
383
387
|
response = {
|
|
384
388
|
text: out.text ?? null,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export async function fetchGeminiWebResource(url, init = {}) {
|
|
2
|
+
try {
|
|
3
|
+
return await fetch(url, init);
|
|
4
|
+
}
|
|
5
|
+
catch (error) {
|
|
6
|
+
const cause = error instanceof Error ? error.cause : undefined;
|
|
7
|
+
if (cause &&
|
|
8
|
+
typeof cause === "object" &&
|
|
9
|
+
"code" in cause &&
|
|
10
|
+
cause.code === "UND_ERR_HEADERS_OVERFLOW") {
|
|
11
|
+
throw new Error("Gemini response headers exceed Node's configured limit. Start Oracle with NODE_OPTIONS=--max-http-header-size=65536 (preserving any existing Node options) and retry.", { cause: error });
|
|
12
|
+
}
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -6,29 +6,29 @@ const MODEL_SPECS = {
|
|
|
6
6
|
hash: "1d44b34bcaa1c04d",
|
|
7
7
|
modelCode: 6,
|
|
8
8
|
thinkingCode: 1,
|
|
9
|
+
capacity: 2,
|
|
9
10
|
},
|
|
10
11
|
"gemini-3.5-flash": {
|
|
11
12
|
hash: "56fdd199312815e2",
|
|
12
13
|
modelCode: 1,
|
|
13
14
|
thinkingCode: 1,
|
|
15
|
+
capacity: 2,
|
|
14
16
|
},
|
|
15
17
|
"gemini-3.1-pro": {
|
|
16
|
-
hash: "
|
|
18
|
+
hash: "e6fa609c3fa255c0",
|
|
17
19
|
modelCode: 3,
|
|
18
20
|
thinkingCode: 1,
|
|
21
|
+
capacity: 2,
|
|
19
22
|
},
|
|
20
23
|
"gemini-3-pro-deep-think": {
|
|
21
|
-
hash: "
|
|
22
|
-
modelCode:
|
|
23
|
-
thinkingCode:
|
|
24
|
+
hash: "e051ce1aa80aa576",
|
|
25
|
+
modelCode: 5,
|
|
26
|
+
thinkingCode: 2,
|
|
27
|
+
capacity: 2,
|
|
24
28
|
},
|
|
25
29
|
};
|
|
26
|
-
|
|
27
|
-
function
|
|
28
|
-
clientId ??= randomUUID().toUpperCase();
|
|
29
|
-
return clientId;
|
|
30
|
-
}
|
|
31
|
-
export function buildGeminiWebModelHeader(model, webClientId = getGeminiWebClientId()) {
|
|
30
|
+
const clientId = randomUUID().toUpperCase();
|
|
31
|
+
export function buildGeminiWebModelHeader(model, webClientId = clientId) {
|
|
32
32
|
const spec = MODEL_SPECS[model];
|
|
33
33
|
return JSON.stringify([
|
|
34
34
|
1,
|
|
@@ -42,7 +42,7 @@ export function buildGeminiWebModelHeader(model, webClientId = getGeminiWebClien
|
|
|
42
42
|
[4, 5, 6, 8],
|
|
43
43
|
null,
|
|
44
44
|
null,
|
|
45
|
-
|
|
45
|
+
spec.capacity,
|
|
46
46
|
null,
|
|
47
47
|
null,
|
|
48
48
|
spec.modelCode,
|
|
@@ -50,6 +50,10 @@ export function buildGeminiWebModelHeader(model, webClientId = getGeminiWebClien
|
|
|
50
50
|
webClientId,
|
|
51
51
|
]);
|
|
52
52
|
}
|
|
53
|
+
export function getGeminiWebModelSelection(model) {
|
|
54
|
+
const { modelCode, thinkingCode } = MODEL_SPECS[model];
|
|
55
|
+
return { modelCode, thinkingCode };
|
|
56
|
+
}
|
|
53
57
|
export function resolveGeminiWebModel(desiredModel, log) {
|
|
54
58
|
const desired = typeof desiredModel === "string" ? desiredModel.trim() : "";
|
|
55
59
|
if (!desired)
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
import "dotenv/config";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
6
|
-
import {
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
6
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
7
7
|
import { getCliVersion } from "../version.js";
|
|
8
8
|
import { registerChatGptImageTool } from "./tools/chatgptImage.js";
|
|
9
9
|
import { registerConsultTool } from "./tools/consult.js";
|
|
10
10
|
import { registerProjectSourcesTool } from "./tools/projectSources.js";
|
|
11
11
|
import { registerSessionsTool } from "./tools/sessions.js";
|
|
12
|
+
import { registerWaitTool } from "./tools/wait.js";
|
|
12
13
|
import { registerSessionResources } from "./tools/sessionResources.js";
|
|
13
|
-
export
|
|
14
|
+
export function createMcpServer() {
|
|
14
15
|
const server = new McpServer({
|
|
15
16
|
name: "oracle-mcp",
|
|
16
17
|
version: getCliVersion(),
|
|
@@ -23,19 +24,15 @@ export async function startMcpServer() {
|
|
|
23
24
|
registerChatGptImageTool(server);
|
|
24
25
|
registerProjectSourcesTool(server);
|
|
25
26
|
registerSessionsTool(server);
|
|
27
|
+
registerWaitTool(server);
|
|
26
28
|
registerSessionResources(server);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
resolve();
|
|
34
|
-
};
|
|
29
|
+
return server;
|
|
30
|
+
}
|
|
31
|
+
export async function startMcpServer() {
|
|
32
|
+
serveStdio(createMcpServer, {
|
|
33
|
+
legacy: "serve",
|
|
34
|
+
onerror: (error) => console.error("MCP transport error:", error),
|
|
35
35
|
});
|
|
36
|
-
// Keep the process alive until the client closes the transport.
|
|
37
|
-
await server.connect(transport);
|
|
38
|
-
await closed;
|
|
39
36
|
}
|
|
40
37
|
export function shouldStartMcpServerFromModule(moduleUrl = import.meta.url, argv1 = process.argv[1]) {
|
|
41
38
|
return argv1 ? moduleUrl === pathToFileURL(argv1).href : false;
|
|
@@ -101,9 +101,9 @@ export function registerChatGptImageTool(server) {
|
|
|
101
101
|
server.registerTool("chatgpt_image", {
|
|
102
102
|
title: "Generate an image with ChatGPT",
|
|
103
103
|
description: "Agent-friendly wrapper for ChatGPT browser image generation. It selects browser mode, enables the image-aware wait/download path, uploads reference files when provided, and returns saved image paths in structuredContent.images.",
|
|
104
|
-
inputSchema: chatGptImageInputShape,
|
|
105
|
-
outputSchema: chatGptImageOutputShape,
|
|
106
|
-
}, async (input) => {
|
|
104
|
+
inputSchema: z.object(chatGptImageInputShape),
|
|
105
|
+
outputSchema: z.object(chatGptImageOutputShape),
|
|
106
|
+
}, async (input, context) => {
|
|
107
107
|
const textContent = (text) => [{ type: "text", text }];
|
|
108
108
|
let parsed;
|
|
109
109
|
try {
|
|
@@ -116,7 +116,7 @@ export function registerChatGptImageTool(server) {
|
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
118
|
const consultInput = buildChatGptImageConsultInput(parsed);
|
|
119
|
-
const result = await runConsultTool(consultInput, {
|
|
119
|
+
const result = await runConsultTool(consultInput, { log: context.mcpReq.log });
|
|
120
120
|
if (result.isError || !result.structuredContent) {
|
|
121
121
|
return result;
|
|
122
122
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getCliVersion } from "../../version.js";
|
|
3
|
-
import { LoggingMessageNotificationParamsSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
3
|
import { ensureBrowserAvailable, mapConsultToRunOptions } from "../utils.js";
|
|
5
4
|
import { sessionStore } from "../../sessionStore.js";
|
|
6
5
|
import { resolveRemoteServiceConfig } from "../../remote/remoteServiceConfig.js";
|
|
7
6
|
import { createRemoteBrowserExecutor } from "../../remote/client.js";
|
|
8
|
-
async function readSessionLogTail(sessionId, maxBytes) {
|
|
7
|
+
export async function readSessionLogTail(sessionId, maxBytes) {
|
|
9
8
|
try {
|
|
10
9
|
const log = await sessionStore.readLog(sessionId);
|
|
11
10
|
if (log.length <= maxBytes) {
|
|
@@ -26,7 +25,9 @@ import { loadUserConfig } from "../../config.js";
|
|
|
26
25
|
import { resolveNotificationSettings } from "../../cli/notifier.js";
|
|
27
26
|
import { mapModelToBrowserLabel, resolveBrowserModelLabel, resolveDefaultBrowserThinkingTime, } from "../../cli/browserConfig.js";
|
|
28
27
|
import { normalizeThinkingTimeLevel } from "../../oracle/thinkingTime.js";
|
|
29
|
-
|
|
28
|
+
import { launchDetachedSession } from "../../cli/detachedSession.js";
|
|
29
|
+
import { buildSessionLifecycle } from "../../cli/sessionLifecycle.js";
|
|
30
|
+
// Shared fields are composed into explicit schemas when registering tools.
|
|
30
31
|
const consultInputShape = {
|
|
31
32
|
preset: z
|
|
32
33
|
.enum(CONSULT_PRESETS)
|
|
@@ -60,11 +61,11 @@ const consultInputShape = {
|
|
|
60
61
|
browserBundleFiles: z
|
|
61
62
|
.boolean()
|
|
62
63
|
.optional()
|
|
63
|
-
.describe("Browser-only: bundle
|
|
64
|
+
.describe("Browser-only: force one upload bundle; auto/zip includes all resolved files. Multiple text/source uploads already bundle by default (flattened text unless ZIP is selected)."),
|
|
64
65
|
browserBundleFormat: z
|
|
65
66
|
.enum(["auto", "text", "zip"])
|
|
66
67
|
.optional()
|
|
67
|
-
.describe('Browser-only: bundle upload format
|
|
68
|
+
.describe('Browser-only: bundle upload format. Defaults to "auto", which keeps flattened text for text-only uploads and uses ZIP when raw files are present; "zip" forces a byte-preserving archive.'),
|
|
68
69
|
browserThinkingTime: browserThinkingTimeRawSchema
|
|
69
70
|
.optional()
|
|
70
71
|
.describe("Browser-only: set ChatGPT thinking time when supported by the chosen model."),
|
|
@@ -96,6 +97,10 @@ const consultInputShape = {
|
|
|
96
97
|
.string()
|
|
97
98
|
.optional()
|
|
98
99
|
.describe("Browser-only image output fallback path, mirroring the CLI --output option for image operations."),
|
|
100
|
+
waitForCompletion: z
|
|
101
|
+
.boolean()
|
|
102
|
+
.optional()
|
|
103
|
+
.describe("When false, start the Oracle run in a detached worker and return its sessionId immediately. Use the wait tool to block for completion without agent-side polling. Defaults to true for compatibility."),
|
|
99
104
|
dryRun: z
|
|
100
105
|
.boolean()
|
|
101
106
|
.optional()
|
|
@@ -109,7 +114,7 @@ const consultInputShape = {
|
|
|
109
114
|
.optional()
|
|
110
115
|
.describe("Optional human-friendly session id (used for later `oracle sessions` lookups)."),
|
|
111
116
|
};
|
|
112
|
-
const consultModelSummaryShape = z.object({
|
|
117
|
+
export const consultModelSummaryShape = z.object({
|
|
113
118
|
model: z.string(),
|
|
114
119
|
status: z.string(),
|
|
115
120
|
startedAt: z.string().optional(),
|
|
@@ -138,14 +143,14 @@ const consultModelSummaryShape = z.object({
|
|
|
138
143
|
.optional(),
|
|
139
144
|
logPath: z.string().optional(),
|
|
140
145
|
});
|
|
141
|
-
const consultArtifactSummaryShape = z.object({
|
|
146
|
+
export const consultArtifactSummaryShape = z.object({
|
|
142
147
|
kind: z.enum(["transcript", "deep-research-report", "image", "file"]),
|
|
143
148
|
path: z.string(),
|
|
144
149
|
label: z.string().optional(),
|
|
145
150
|
mimeType: z.string().optional(),
|
|
146
151
|
sizeBytes: z.number().optional(),
|
|
147
152
|
});
|
|
148
|
-
const consultImageSummaryShape = consultArtifactSummaryShape.extend({
|
|
153
|
+
export const consultImageSummaryShape = consultArtifactSummaryShape.extend({
|
|
149
154
|
kind: z.literal("image"),
|
|
150
155
|
alt: z.string().optional(),
|
|
151
156
|
width: z.number().optional(),
|
|
@@ -185,6 +190,7 @@ export const consultOutputShape = {
|
|
|
185
190
|
models: z.array(consultModelSummaryShape).optional(),
|
|
186
191
|
artifacts: z.array(consultArtifactSummaryShape).optional(),
|
|
187
192
|
images: z.array(consultImageSummaryShape).optional(),
|
|
193
|
+
detached: z.boolean().optional(),
|
|
188
194
|
};
|
|
189
195
|
export function summarizeModelRunsForConsult(runs) {
|
|
190
196
|
if (!runs || runs.length === 0) {
|
|
@@ -387,7 +393,7 @@ export function formatConsultDryRunResolved(details) {
|
|
|
387
393
|
}
|
|
388
394
|
return lines;
|
|
389
395
|
}
|
|
390
|
-
export async function runConsultTool(input, {
|
|
396
|
+
export async function runConsultTool(input, { log: requestLog, launchDetached = launchDetachedSession, }) {
|
|
391
397
|
const textContent = (text) => [{ type: "text", text }];
|
|
392
398
|
let parsedInput;
|
|
393
399
|
try {
|
|
@@ -399,7 +405,7 @@ export async function runConsultTool(input, { server }) {
|
|
|
399
405
|
content: textContent(error instanceof Error ? error.message : String(error)),
|
|
400
406
|
};
|
|
401
407
|
}
|
|
402
|
-
const { prompt, files, model, models, engine, search, browserModelLabel, browserAttachments, browserBundleFiles, browserBundleFormat, browserThinkingTime, browserModelStrategy, browserResearchMode, browserArchive, browserFollowUps, browserKeepBrowser, generateImage, outputPath, dryRun, slug, } = parsedInput;
|
|
408
|
+
const { prompt, files, model, models, engine, search, browserModelLabel, browserAttachments, browserBundleFiles, browserBundleFormat, browserThinkingTime, browserModelStrategy, browserResearchMode, browserArchive, browserFollowUps, browserKeepBrowser, generateImage, outputPath, waitForCompletion = true, dryRun, slug, } = parsedInput;
|
|
403
409
|
const { config: userConfig } = await loadUserConfig();
|
|
404
410
|
let runOptions;
|
|
405
411
|
let resolvedEngine;
|
|
@@ -428,12 +434,7 @@ export async function runConsultTool(input, { server }) {
|
|
|
428
434
|
};
|
|
429
435
|
}
|
|
430
436
|
const cwd = process.cwd();
|
|
431
|
-
const sendLog = (text, level = "info") =>
|
|
432
|
-
.sendLoggingMessage(LoggingMessageNotificationParamsSchema.parse({
|
|
433
|
-
level,
|
|
434
|
-
data: { text, bytes: Buffer.byteLength(text, "utf8") },
|
|
435
|
-
}))
|
|
436
|
-
.catch(() => { });
|
|
437
|
+
const sendLog = (text, level = "info") => requestLog(level, { text, bytes: Buffer.byteLength(text, "utf8") }).catch(() => { });
|
|
437
438
|
const resolvedRemote = resolveRemoteServiceConfig({ userConfig, env: process.env });
|
|
438
439
|
const imageOutputPath = runOptions.generateImage ?? runOptions.outputPath;
|
|
439
440
|
if (resolvedEngine === "browser" && resolvedRemote.host && imageOutputPath) {
|
|
@@ -490,6 +491,18 @@ export async function runConsultTool(input, { server }) {
|
|
|
490
491
|
},
|
|
491
492
|
};
|
|
492
493
|
}
|
|
494
|
+
if (!waitForCompletion && resolvedEngine === "browser" && resolvedRemote.host) {
|
|
495
|
+
return {
|
|
496
|
+
isError: true,
|
|
497
|
+
content: textContent("Detached MCP consults are not supported with a remote browser service yet. Keep waitForCompletion:true for this run."),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
if (!waitForCompletion && process.env.ORACLE_NO_DETACH === "1") {
|
|
501
|
+
return {
|
|
502
|
+
isError: true,
|
|
503
|
+
content: textContent("Detached MCP consults are disabled by ORACLE_NO_DETACH=1. Remove it or keep waitForCompletion:true."),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
493
506
|
const browserGuard = ensureBrowserAvailable(resolvedEngine, {
|
|
494
507
|
remoteHost: resolvedRemote.host,
|
|
495
508
|
});
|
|
@@ -525,8 +538,58 @@ export async function runConsultTool(input, { server }) {
|
|
|
525
538
|
mode: resolvedEngine,
|
|
526
539
|
slug,
|
|
527
540
|
browserConfig,
|
|
528
|
-
waitPreference:
|
|
541
|
+
waitPreference: waitForCompletion,
|
|
529
542
|
}, cwd, notifications);
|
|
543
|
+
if (!waitForCompletion) {
|
|
544
|
+
try {
|
|
545
|
+
await launchDetached({
|
|
546
|
+
sessionId: sessionMeta.id,
|
|
547
|
+
prepare: async (workerPid) => {
|
|
548
|
+
const lifecycle = buildSessionLifecycle({
|
|
549
|
+
engine: resolvedEngine,
|
|
550
|
+
detached: true,
|
|
551
|
+
workerPid,
|
|
552
|
+
reattachCommand: `oracle session ${sessionMeta.id}`,
|
|
553
|
+
});
|
|
554
|
+
await sessionStore.updateSession(sessionMeta.id, {
|
|
555
|
+
status: "running",
|
|
556
|
+
startedAt: new Date().toISOString(),
|
|
557
|
+
lifecycle,
|
|
558
|
+
});
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
const started = (await sessionStore.readSession(sessionMeta.id)) ?? sessionMeta;
|
|
562
|
+
const summary = `Session ${sessionMeta.id} (${started.status}; detached)`;
|
|
563
|
+
return {
|
|
564
|
+
content: textContent(`${summary}\nUse the wait tool with id=${JSON.stringify(sessionMeta.id)} to wait without polling.`),
|
|
565
|
+
structuredContent: {
|
|
566
|
+
sessionId: sessionMeta.id,
|
|
567
|
+
status: started.status,
|
|
568
|
+
output: "",
|
|
569
|
+
models: summarizeModelRunsForConsult(started.models),
|
|
570
|
+
artifacts: summarizeArtifactsForConsult(started.artifacts),
|
|
571
|
+
images: summarizeImageArtifactsForConsult(started.artifacts),
|
|
572
|
+
detached: true,
|
|
573
|
+
},
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
578
|
+
await sessionStore
|
|
579
|
+
.updateSession(sessionMeta.id, {
|
|
580
|
+
status: "error",
|
|
581
|
+
completedAt: new Date().toISOString(),
|
|
582
|
+
errorMessage: message,
|
|
583
|
+
response: { status: "error" },
|
|
584
|
+
error: { category: "internal", message },
|
|
585
|
+
})
|
|
586
|
+
.catch(() => undefined);
|
|
587
|
+
return {
|
|
588
|
+
isError: true,
|
|
589
|
+
content: textContent(`Unable to start detached session ${sessionMeta.id}: ${message}`),
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
}
|
|
530
593
|
const logWriter = sessionStore.createLogWriter(sessionMeta.id);
|
|
531
594
|
// Stream logs to both the session log and MCP logging notifications, but avoid buffering in memory
|
|
532
595
|
const log = (line) => {
|
|
@@ -594,9 +657,8 @@ export async function runConsultTool(input, { server }) {
|
|
|
594
657
|
export function registerConsultTool(server) {
|
|
595
658
|
server.registerTool("consult", {
|
|
596
659
|
title: "Run an oracle session",
|
|
597
|
-
description: 'Run an Oracle session (API or ChatGPT browser automation). Use `files` to attach project context. If `engine` is omitted, Oracle follows CLI defaults: config/ORACLE_ENGINE first, then API when OPENAI_API_KEY is set, otherwise browser. Browser GPT-5.5 Pro consults can take many minutes; use `dryRun:true` first when configuring an agent and inspect `sessions`/`oracle status` before retrying. Browser manual-login uses a private Oracle Chrome profile separate from the user\'s normal Chrome; dry-run output includes first-time setup guidance when that path is active. For browser-based image/file uploads, set `browserAttachments:"always"`. For ChatGPT image generation, set `generateImage` to enable the same image wait/download path as CLI --generate-image and read returned paths from `images`. Browser consults can include `browserFollowUps` for a multi-turn ChatGPT review in one conversation. Sessions are stored under `ORACLE_HOME_DIR` (shared with the CLI).',
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}, async (input) => runConsultTool(input, { server: server.server }));
|
|
660
|
+
description: 'Run an Oracle session (API or ChatGPT browser automation). Use `files` to attach project context. If `engine` is omitted, Oracle follows CLI defaults: config/ORACLE_ENGINE first, then API when OPENAI_API_KEY is set, otherwise browser. Browser GPT-5.5 Pro consults can take many minutes; set `waitForCompletion:false` to return a durable sessionId immediately, then use `wait` to block without agent-side polling. Use `dryRun:true` first when configuring an agent and inspect `sessions`/`oracle status` before retrying. Browser manual-login uses a private Oracle Chrome profile separate from the user\'s normal Chrome; dry-run output includes first-time setup guidance when that path is active. For browser-based image/file uploads, set `browserAttachments:"always"`. For ChatGPT image generation, set `generateImage` to enable the same image wait/download path as CLI --generate-image and read returned paths from `images`. Browser consults can include `browserFollowUps` for a multi-turn ChatGPT review in one conversation. Sessions are stored under `ORACLE_HOME_DIR` (shared with the CLI).',
|
|
661
|
+
inputSchema: z.object(consultInputShape),
|
|
662
|
+
outputSchema: z.object(consultOutputShape),
|
|
663
|
+
}, async (input, context) => runConsultTool(input, { log: context.mcpReq.log }));
|
|
602
664
|
}
|
|
@@ -56,9 +56,9 @@ export function registerProjectSourcesTool(server) {
|
|
|
56
56
|
server.registerTool("project_sources", {
|
|
57
57
|
title: "Manage ChatGPT Project Sources",
|
|
58
58
|
description: "List or append files to a ChatGPT Project's persistent Sources tab. This is useful for Developer Mode workflows where chats do not share memory, but explicit project sources provide shared context. Destructive delete/replace/sync operations are intentionally not included in v1.",
|
|
59
|
-
inputSchema: projectSourcesInputShape,
|
|
60
|
-
outputSchema: projectSourcesOutputShape,
|
|
61
|
-
}, async (input) => {
|
|
59
|
+
inputSchema: z.object(projectSourcesInputShape),
|
|
60
|
+
outputSchema: z.object(projectSourcesOutputShape),
|
|
61
|
+
}, async (input, context) => {
|
|
62
62
|
const textContent = (text) => [{ type: "text", text }];
|
|
63
63
|
let parsed;
|
|
64
64
|
try {
|
|
@@ -107,9 +107,7 @@ export function registerProjectSourcesTool(server) {
|
|
|
107
107
|
dryRun: parsed.dryRun,
|
|
108
108
|
config: browserConfig,
|
|
109
109
|
log: (message) => {
|
|
110
|
-
|
|
111
|
-
.sendLoggingMessage({ level: "info", data: { text: message } })
|
|
112
|
-
.catch(() => undefined);
|
|
110
|
+
context.mcpReq.log("info", { text: message }).catch(() => undefined);
|
|
113
111
|
},
|
|
114
112
|
});
|
|
115
113
|
const output = result.status === "dry-run"
|
|
@@ -44,8 +44,8 @@ export function registerSessionsTool(server) {
|
|
|
44
44
|
server.registerTool("sessions", {
|
|
45
45
|
title: "List or fetch oracle sessions",
|
|
46
46
|
description: "Inspect Oracle session history stored under `ORACLE_HOME_DIR` (shared with the CLI). List recent sessions or fetch one by id/slug (optionally including metadata + request + log).",
|
|
47
|
-
inputSchema: sessionsInputShape,
|
|
48
|
-
outputSchema: sessionsOutputShape,
|
|
47
|
+
inputSchema: z.object(sessionsInputShape),
|
|
48
|
+
outputSchema: z.object(sessionsOutputShape),
|
|
49
49
|
}, async (input) => {
|
|
50
50
|
const textContent = (text) => [{ type: "text", text }];
|
|
51
51
|
const { id, hours = 24, limit = 100, includeAll = false, detail = false, } = sessionsInputSchema.parse(input);
|