@steipete/oracle 0.17.3 → 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 +75 -64
- 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 +267 -15
- 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 +15 -3
- package/dist/src/browser/detect.js +1 -1
- package/dist/src/browser/index.js +447 -263
- package/dist/src/browser/modelDisplay.js +9 -0
- package/dist/src/browser/pageActions.js +2 -2
- package/dist/src/browser/policies.js +6 -2
- package/dist/src/browser/profileState.js +114 -9
- package/dist/src/browser/projectSourcesRunner.js +88 -17
- package/dist/src/browser/prompt.js +272 -113
- package/dist/src/browser/providers/chatgptDomProvider.js +2 -0
- package/dist/src/browser/reattach.js +14 -4
- 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 +40 -15
- package/dist/src/cli/browserDefaults.js +13 -0
- package/dist/src/cli/browserTabs.js +12 -29
- package/dist/src/cli/detachedSession.js +69 -0
- package/dist/src/cli/dryRun.js +58 -47
- package/dist/src/cli/harvestIntegrity.js +140 -0
- package/dist/src/cli/projectSources.js +5 -3
- 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 +97 -26
- 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 +351 -72
- 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
|
@@ -12,7 +12,7 @@ import { estimateTokenCount } from "../browser/utils.js";
|
|
|
12
12
|
import { formatSessionTableHeader, formatSessionTableRow, resolveSessionCost, } from "./sessionTable.js";
|
|
13
13
|
import { abbreviateResponseId, buildResponseOwnerIndex, resolveSessionLineage, } from "./sessionLineage.js";
|
|
14
14
|
import { formatSessionExecutionLabel } from "./sessionLifecycle.js";
|
|
15
|
-
import { formatBrowserModelSelectionEvidence, formatSessionBrowserModelWithRequestedKey, resolveSessionBrowserModelDisplayName, } from "../browser/modelDisplay.js";
|
|
15
|
+
import { formatBrowserModelSelectionEvidence, formatBrowserThinkingSelectionEvidence, formatSessionBrowserModelWithRequestedKey, resolveSessionBrowserModelDisplayName, } from "../browser/modelDisplay.js";
|
|
16
16
|
const isTty = () => Boolean(process.stdout.isTTY);
|
|
17
17
|
const dim = (text) => (isTty() ? kleur.dim(text) : text);
|
|
18
18
|
export const MAX_RENDER_BYTES = 200_000;
|
|
@@ -255,6 +255,7 @@ export async function attachSession(sessionId, options) {
|
|
|
255
255
|
config: metadata.browser?.config,
|
|
256
256
|
runtime,
|
|
257
257
|
modelSelection: metadata.browser?.modelSelection,
|
|
258
|
+
thinkingSelection: metadata.browser?.thinkingSelection,
|
|
258
259
|
warnings: metadata.browser?.warnings,
|
|
259
260
|
},
|
|
260
261
|
artifacts,
|
|
@@ -603,7 +604,9 @@ export function formatUserErrorMetadata(metadata) {
|
|
|
603
604
|
}
|
|
604
605
|
export function formatBrowserEvidence(metadata) {
|
|
605
606
|
const browser = metadata.browser;
|
|
606
|
-
if (!browser?.modelSelection &&
|
|
607
|
+
if (!browser?.modelSelection &&
|
|
608
|
+
!browser?.thinkingSelection &&
|
|
609
|
+
(!browser?.warnings || browser.warnings.length === 0)) {
|
|
607
610
|
return null;
|
|
608
611
|
}
|
|
609
612
|
const lines = [];
|
|
@@ -611,6 +614,10 @@ export function formatBrowserEvidence(metadata) {
|
|
|
611
614
|
if (evidence) {
|
|
612
615
|
lines.push(`model ${formatBrowserModelSelectionEvidence(evidence, metadata.model)}`);
|
|
613
616
|
}
|
|
617
|
+
const thinkingEvidence = browser.thinkingSelection;
|
|
618
|
+
if (thinkingEvidence) {
|
|
619
|
+
lines.push(`effort ${formatBrowserThinkingSelectionEvidence(thinkingEvidence)}`);
|
|
620
|
+
}
|
|
614
621
|
for (const warning of browser.warnings ?? []) {
|
|
615
622
|
lines.push(`warning ${warning.code}: ${warning.message}`);
|
|
616
623
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import kleur from "kleur";
|
|
2
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
3
|
import fs from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { runOracle, OracleResponseError, OracleTransportError, extractResponseMetadata, asOracleUserError, extractTextOutput, classifyProviderFailure, } from "../oracle.js";
|
|
@@ -23,6 +24,7 @@ import { cwd as getCwd } from "node:process";
|
|
|
23
24
|
import { resumeBrowserSession } from "../browser/reattach.js";
|
|
24
25
|
import { hasRecoverableChatGptConversation } from "../browser/reattachability.js";
|
|
25
26
|
import { estimateTokenCount } from "../browser/utils.js";
|
|
27
|
+
import { computeFileSha256, sanitizeArtifactFilename } from "../browser/artifacts.js";
|
|
26
28
|
import { formatElapsed } from "../oracle/format.js";
|
|
27
29
|
import { formatBrowserReattachGuidance } from "./reattachGuidance.js";
|
|
28
30
|
const isTty = process.stdout.isTTY;
|
|
@@ -78,7 +80,13 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
78
80
|
cwd,
|
|
79
81
|
log,
|
|
80
82
|
}, runnerDeps);
|
|
81
|
-
await writeAssistantOutput(runOptions.writeOutputPath, result.answerText ?? "", log);
|
|
83
|
+
const writtenOutputPath = await writeAssistantOutput(runOptions.writeOutputPath, result.answerText ?? "", log);
|
|
84
|
+
const outputArtifacts = await copyBrowserOutputArtifacts({
|
|
85
|
+
outputPath: writtenOutputPath,
|
|
86
|
+
savedFiles: runOptions.writeArtifacts ? result.savedFiles : undefined,
|
|
87
|
+
log,
|
|
88
|
+
});
|
|
89
|
+
const browserWarnings = [...(result.warnings ?? []), ...outputArtifacts.warnings];
|
|
82
90
|
await sendSessionNotification({
|
|
83
91
|
sessionId: sessionMeta.id,
|
|
84
92
|
sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
|
|
@@ -105,9 +113,10 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
105
113
|
runtime: result.runtime,
|
|
106
114
|
archive: result.archive,
|
|
107
115
|
modelSelection: result.modelSelection,
|
|
108
|
-
|
|
116
|
+
thinkingSelection: result.thinkingSelection,
|
|
117
|
+
warnings: browserWarnings.length > 0 ? browserWarnings : undefined,
|
|
109
118
|
},
|
|
110
|
-
artifacts: mergeArtifacts(sessionMeta.artifacts, result.artifacts),
|
|
119
|
+
artifacts: mergeArtifacts(sessionMeta.artifacts, mergeArtifacts(result.artifacts, outputArtifacts.artifacts)),
|
|
111
120
|
response: undefined,
|
|
112
121
|
transport: undefined,
|
|
113
122
|
error: undefined,
|
|
@@ -848,6 +857,81 @@ function resolveSessionPath(sessionDir, targetPath) {
|
|
|
848
857
|
}
|
|
849
858
|
return path.join(sessionDir, targetPath);
|
|
850
859
|
}
|
|
860
|
+
async function copyBrowserOutputArtifacts(params) {
|
|
861
|
+
const artifacts = [];
|
|
862
|
+
const warnings = [];
|
|
863
|
+
if (!params.outputPath || !params.savedFiles?.length) {
|
|
864
|
+
return { artifacts, warnings };
|
|
865
|
+
}
|
|
866
|
+
const outputDir = path.dirname(params.outputPath);
|
|
867
|
+
for (const savedFile of params.savedFiles) {
|
|
868
|
+
const filename = sanitizeArtifactFilename(savedFile.filename ?? path.basename(savedFile.path), "artifact.bin");
|
|
869
|
+
let copied;
|
|
870
|
+
try {
|
|
871
|
+
copied = await copyFileWithoutOverwrite(savedFile.path, path.join(outputDir, filename));
|
|
872
|
+
const [stat, sha256] = await Promise.all([fs.stat(copied), computeFileSha256(copied)]);
|
|
873
|
+
if (savedFile.sizeBytes != null && stat.size !== savedFile.sizeBytes) {
|
|
874
|
+
throw new Error(`size mismatch (expected ${savedFile.sizeBytes}, received ${stat.size})`);
|
|
875
|
+
}
|
|
876
|
+
if (savedFile.sha256 && sha256 !== savedFile.sha256) {
|
|
877
|
+
throw new Error(`sha256 mismatch (expected ${savedFile.sha256}, received ${sha256})`);
|
|
878
|
+
}
|
|
879
|
+
const artifact = {
|
|
880
|
+
kind: "file",
|
|
881
|
+
path: copied,
|
|
882
|
+
label: `${savedFile.label ?? filename} (write-output)`,
|
|
883
|
+
mimeType: savedFile.mimeType,
|
|
884
|
+
sizeBytes: stat.size,
|
|
885
|
+
sourceUrl: savedFile.sourceUrl,
|
|
886
|
+
sha256,
|
|
887
|
+
validation: savedFile.validation,
|
|
888
|
+
transfer: { status: "not-needed" },
|
|
889
|
+
origin: { mode: "local" },
|
|
890
|
+
};
|
|
891
|
+
artifacts.push(artifact);
|
|
892
|
+
params.log(dim(`[browser] Saved write-output file artifact to ${copied} sha256=${sha256}`));
|
|
893
|
+
}
|
|
894
|
+
catch (error) {
|
|
895
|
+
if (copied) {
|
|
896
|
+
await fs.unlink(copied).catch(() => undefined);
|
|
897
|
+
}
|
|
898
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
899
|
+
const message = `Failed to copy saved file artifact ${savedFile.path} beside ${params.outputPath}: ${reason}`;
|
|
900
|
+
params.log(dim(`[browser] ${message}`));
|
|
901
|
+
warnings.push({
|
|
902
|
+
code: "browser-output-artifact-copy-failed",
|
|
903
|
+
severity: "warning",
|
|
904
|
+
message,
|
|
905
|
+
details: {
|
|
906
|
+
sourcePath: savedFile.path,
|
|
907
|
+
outputPath: params.outputPath,
|
|
908
|
+
sha256: savedFile.sha256,
|
|
909
|
+
},
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return { artifacts, warnings };
|
|
914
|
+
}
|
|
915
|
+
async function copyFileWithoutOverwrite(sourcePath, baseTargetPath) {
|
|
916
|
+
const ext = path.extname(baseTargetPath);
|
|
917
|
+
const stem = ext ? path.basename(baseTargetPath, ext) : path.basename(baseTargetPath);
|
|
918
|
+
const dir = path.dirname(baseTargetPath);
|
|
919
|
+
let suffix = 1;
|
|
920
|
+
for (;;) {
|
|
921
|
+
const targetPath = suffix === 1 ? baseTargetPath : path.join(dir, `${stem}-${suffix}${ext}`);
|
|
922
|
+
try {
|
|
923
|
+
await fs.copyFile(sourcePath, targetPath, fsConstants.COPYFILE_EXCL);
|
|
924
|
+
return targetPath;
|
|
925
|
+
}
|
|
926
|
+
catch (error) {
|
|
927
|
+
if (isErrorCode(error, "EEXIST")) {
|
|
928
|
+
suffix += 1;
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
throw error;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
851
935
|
async function writeAssistantOutput(targetPath, content, log) {
|
|
852
936
|
if (!targetPath)
|
|
853
937
|
return;
|
|
@@ -1057,11 +1141,11 @@ export function deriveModelOutputPath(basePath, model) {
|
|
|
1057
1141
|
const suffix = ext.length > 0 ? `${stem}.${model}${ext}` : `${stem}.${model}`;
|
|
1058
1142
|
return path.join(dir, suffix);
|
|
1059
1143
|
}
|
|
1144
|
+
function isErrorCode(error, expected) {
|
|
1145
|
+
return error instanceof Error && error.code === expected;
|
|
1146
|
+
}
|
|
1060
1147
|
function isPermissionError(error) {
|
|
1061
|
-
|
|
1062
|
-
return false;
|
|
1063
|
-
const code = error.code;
|
|
1064
|
-
return code === "EACCES" || code === "EPERM";
|
|
1148
|
+
return isErrorCode(error, "EACCES") || isErrorCode(error, "EPERM");
|
|
1065
1149
|
}
|
|
1066
1150
|
function buildFallbackPath(original) {
|
|
1067
1151
|
const ext = path.extname(original);
|
package/dist/src/config.js
CHANGED
|
@@ -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
|
}
|