@steipete/oracle 0.9.0 → 0.11.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/LICENSE +1 -1
- package/README.md +107 -49
- package/dist/bin/oracle-cli.js +551 -410
- package/dist/bin/oracle-mcp.js +2 -2
- package/dist/bin/oracle.js +165 -279
- package/dist/scripts/agent-send.js +31 -31
- package/dist/scripts/check.js +6 -6
- package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
- package/dist/scripts/docs-list.js +30 -30
- package/dist/scripts/git-policy.js +25 -23
- package/dist/scripts/run-cli.js +8 -8
- package/dist/scripts/runner.js +203 -195
- package/dist/scripts/test-browser.js +21 -18
- package/dist/scripts/test-remote-chrome.js +20 -20
- package/dist/src/bridge/connection.js +18 -18
- package/dist/src/bridge/userConfigFile.js +7 -7
- package/dist/src/browser/actions/archiveConversation.js +224 -0
- package/dist/src/browser/actions/assistantResponse.js +175 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/deepResearch.js +662 -0
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +342 -119
- package/dist/src/browser/actions/navigation.js +183 -137
- package/dist/src/browser/actions/projectSources.js +491 -0
- package/dist/src/browser/actions/promptComposer.js +152 -91
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingStatus.js +391 -0
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/artifacts.js +150 -0
- package/dist/src/browser/attachRunning.js +31 -0
- package/dist/src/browser/chatgptImages.js +315 -0
- package/dist/src/browser/chromeLifecycle.js +276 -63
- package/dist/src/browser/config.js +59 -16
- package/dist/src/browser/constants.js +25 -12
- package/dist/src/browser/controlPlan.js +81 -0
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +250 -77
- package/dist/src/browser/domDebug.js +50 -1
- package/dist/src/browser/index.js +1559 -692
- package/dist/src/browser/liveTabs.js +434 -0
- package/dist/src/browser/modelStrategy.js +1 -1
- package/dist/src/browser/pageActions.js +5 -5
- package/dist/src/browser/policies.js +16 -13
- package/dist/src/browser/profileState.js +127 -42
- package/dist/src/browser/projectSourcesRunner.js +366 -0
- package/dist/src/browser/prompt.js +72 -42
- package/dist/src/browser/promptSummary.js +5 -5
- package/dist/src/browser/providerDomFlow.js +1 -1
- package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
- package/dist/src/browser/providers/index.js +2 -2
- package/dist/src/browser/reattach.js +178 -73
- package/dist/src/browser/reattachHelpers.js +32 -27
- package/dist/src/browser/sessionRunner.js +89 -25
- package/dist/src/browser/tabLeaseRegistry.js +182 -0
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +24 -20
- package/dist/src/cli/bridge/client.js +28 -20
- package/dist/src/cli/bridge/codexConfig.js +16 -16
- package/dist/src/cli/bridge/doctor.js +47 -39
- package/dist/src/cli/bridge/host.js +58 -56
- package/dist/src/cli/browserConfig.js +102 -48
- package/dist/src/cli/browserDefaults.js +51 -26
- package/dist/src/cli/browserTabs.js +228 -0
- package/dist/src/cli/bundleWarnings.js +1 -1
- package/dist/src/cli/clipboard.js +11 -2
- package/dist/src/cli/detach.js +2 -2
- package/dist/src/cli/dryRun.js +62 -26
- package/dist/src/cli/duplicatePromptGuard.js +12 -4
- package/dist/src/cli/engine.js +9 -9
- package/dist/src/cli/errorUtils.js +1 -1
- package/dist/src/cli/fileSize.js +3 -3
- package/dist/src/cli/format.js +2 -2
- package/dist/src/cli/help.js +28 -28
- package/dist/src/cli/hiddenAliases.js +3 -3
- package/dist/src/cli/markdownBundle.js +7 -7
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +131 -106
- package/dist/src/cli/oscUtils.js +1 -1
- package/dist/src/cli/projectSources.js +116 -0
- package/dist/src/cli/promptRequirement.js +2 -2
- package/dist/src/cli/renderOutput.js +1 -1
- package/dist/src/cli/rootAlias.js +1 -1
- package/dist/src/cli/runOptions.js +32 -28
- package/dist/src/cli/sessionCommand.js +82 -21
- package/dist/src/cli/sessionDisplay.js +213 -87
- package/dist/src/cli/sessionLineage.js +6 -2
- package/dist/src/cli/sessionRunner.js +149 -95
- package/dist/src/cli/sessionTable.js +26 -23
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +139 -128
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +19 -15
- package/dist/src/gemini-web/client.js +76 -70
- package/dist/src/gemini-web/executionMode.js +6 -8
- package/dist/src/gemini-web/executor.js +98 -93
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/consultPresets.js +19 -0
- package/dist/src/mcp/server.js +18 -12
- package/dist/src/mcp/tools/consult.js +246 -67
- package/dist/src/mcp/tools/projectSources.js +123 -0
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +12 -5
- package/dist/src/mcp/utils.js +21 -8
- package/dist/src/oracle/background.js +15 -15
- package/dist/src/oracle/claude.js +53 -25
- package/dist/src/oracle/client.js +50 -41
- package/dist/src/oracle/config.js +96 -66
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +55 -46
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -33
- package/dist/src/oracle/logging.js +7 -7
- package/dist/src/oracle/markdown.js +28 -28
- package/dist/src/oracle/modelResolver.js +16 -16
- package/dist/src/oracle/multiModelRunner.js +12 -12
- package/dist/src/oracle/oscProgress.js +8 -8
- package/dist/src/oracle/promptAssembly.js +6 -3
- package/dist/src/oracle/request.js +16 -13
- package/dist/src/oracle/run.js +160 -135
- package/dist/src/oracle/runUtils.js +8 -5
- package/dist/src/oracle/tokenEstimate.js +6 -6
- package/dist/src/oracle/tokenStats.js +5 -5
- package/dist/src/oracle/tokenStringifier.js +5 -5
- package/dist/src/oracle.js +12 -12
- package/dist/src/oracleHome.js +3 -3
- package/dist/src/projectSources/plan.js +27 -0
- package/dist/src/projectSources/url.js +23 -0
- package/dist/src/remote/client.js +25 -25
- package/dist/src/remote/health.js +20 -20
- package/dist/src/remote/remoteServiceConfig.js +9 -9
- package/dist/src/remote/server.js +129 -118
- package/dist/src/sessionManager.js +78 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +67 -62
- package/vendor/oracle-notifier/README.md +2 -0
- 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/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/profileSync.js +0 -141
- /package/dist/{oracle/src/browser → src/projectSources}/types.js +0 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ASSISTANT_ROLE_SELECTOR, CONVERSATION_TURN_SELECTOR } from "./constants.js";
|
|
4
|
+
import { delay } from "./utils.js";
|
|
5
|
+
import { readAssistantSnapshot } from "./pageActions.js";
|
|
6
|
+
import { getOracleHomeDir } from "../oracleHome.js";
|
|
7
|
+
import { resolveSessionArtifactsDir } from "./artifacts.js";
|
|
8
|
+
const GENERATED_IMAGE_WAIT_MIN_MS = 15_000;
|
|
9
|
+
const GENERATED_IMAGE_WAIT_MAX_MS = 15 * 60_000;
|
|
10
|
+
function extractFileId(url) {
|
|
11
|
+
try {
|
|
12
|
+
return new URL(url).searchParams.get("id") ?? undefined;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function dedupeImages(images) {
|
|
19
|
+
const best = new Map();
|
|
20
|
+
for (const image of images) {
|
|
21
|
+
const key = image.fileId ?? image.url;
|
|
22
|
+
const currentArea = (image.width ?? 0) * (image.height ?? 0);
|
|
23
|
+
const existing = best.get(key);
|
|
24
|
+
const existingArea = existing ? (existing.width ?? 0) * (existing.height ?? 0) : -1;
|
|
25
|
+
if (!existing || currentArea >= existingArea) {
|
|
26
|
+
best.set(key, image);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return [...best.values()];
|
|
30
|
+
}
|
|
31
|
+
function buildAssistantImageExpression(minTurnIndex) {
|
|
32
|
+
const minTurnLiteral = typeof minTurnIndex === "number" && Number.isFinite(minTurnIndex) && minTurnIndex >= 0
|
|
33
|
+
? Math.floor(minTurnIndex)
|
|
34
|
+
: -1;
|
|
35
|
+
const conversationLiteral = JSON.stringify(CONVERSATION_TURN_SELECTOR);
|
|
36
|
+
const assistantLiteral = JSON.stringify(ASSISTANT_ROLE_SELECTOR);
|
|
37
|
+
return `(() => {
|
|
38
|
+
const MIN_TURN_INDEX = ${minTurnLiteral};
|
|
39
|
+
const CONVERSATION_SELECTOR = ${conversationLiteral};
|
|
40
|
+
const ASSISTANT_SELECTOR = ${assistantLiteral};
|
|
41
|
+
const isGeneratedImage = (img) => {
|
|
42
|
+
const url = img?.src || '';
|
|
43
|
+
if (!url.includes('/backend-api/estuary/content?id=file_')) return false;
|
|
44
|
+
const alt = String(img.alt || '').toLowerCase();
|
|
45
|
+
if (alt.includes('generated image')) return true;
|
|
46
|
+
let node = img;
|
|
47
|
+
while (node instanceof HTMLElement) {
|
|
48
|
+
if (String(node.id || '').startsWith('image-')) return true;
|
|
49
|
+
if (String(node.className || '').includes('imagegen-image')) return true;
|
|
50
|
+
node = node.parentElement;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
};
|
|
54
|
+
const serializeImages = (root) =>
|
|
55
|
+
Array.from(root.querySelectorAll('img')).filter(isGeneratedImage).map((img) => ({
|
|
56
|
+
url: img.src || '',
|
|
57
|
+
alt: img.alt || '',
|
|
58
|
+
width: img.naturalWidth || 0,
|
|
59
|
+
height: img.naturalHeight || 0,
|
|
60
|
+
}));
|
|
61
|
+
const isAssistantTurn = (node) => {
|
|
62
|
+
if (!(node instanceof HTMLElement)) return false;
|
|
63
|
+
const turnAttr = (node.getAttribute('data-turn') || node.dataset?.turn || '').toLowerCase();
|
|
64
|
+
if (turnAttr === 'assistant') return true;
|
|
65
|
+
const role = (node.getAttribute('data-message-author-role') || node.dataset?.messageAuthorRole || '').toLowerCase();
|
|
66
|
+
if (role === 'assistant') return true;
|
|
67
|
+
const testId = (node.getAttribute('data-testid') || '').toLowerCase();
|
|
68
|
+
if (testId.includes('assistant')) return true;
|
|
69
|
+
return Boolean(node.querySelector(ASSISTANT_SELECTOR) || node.querySelector('[data-testid*="assistant"]'));
|
|
70
|
+
};
|
|
71
|
+
const turns = Array.from(document.querySelectorAll(CONVERSATION_SELECTOR));
|
|
72
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
73
|
+
const turn = turns[index];
|
|
74
|
+
if (!isAssistantTurn(turn)) continue;
|
|
75
|
+
if (MIN_TURN_INDEX >= 0 && index < MIN_TURN_INDEX) continue;
|
|
76
|
+
const messageRoot = turn.querySelector(ASSISTANT_SELECTOR) || turn;
|
|
77
|
+
const images = serializeImages(messageRoot);
|
|
78
|
+
if (images.length > 0) return images;
|
|
79
|
+
}
|
|
80
|
+
const boundary =
|
|
81
|
+
MIN_TURN_INDEX > 0 && turns.length > 0
|
|
82
|
+
? turns[Math.min(MIN_TURN_INDEX - 1, turns.length - 1)]
|
|
83
|
+
: null;
|
|
84
|
+
return Array.from(document.querySelectorAll('img'))
|
|
85
|
+
.filter(isGeneratedImage)
|
|
86
|
+
.filter((img) => {
|
|
87
|
+
if (!boundary) return true;
|
|
88
|
+
return Boolean(boundary.compareDocumentPosition(img) & Node.DOCUMENT_POSITION_FOLLOWING);
|
|
89
|
+
})
|
|
90
|
+
.map((img) => ({
|
|
91
|
+
url: img.src || '',
|
|
92
|
+
alt: img.alt || '',
|
|
93
|
+
width: img.naturalWidth || 0,
|
|
94
|
+
height: img.naturalHeight || 0,
|
|
95
|
+
}));
|
|
96
|
+
})()`;
|
|
97
|
+
}
|
|
98
|
+
export async function readAssistantGeneratedImages(Runtime, minTurnIndex) {
|
|
99
|
+
const { result } = await Runtime.evaluate({
|
|
100
|
+
expression: buildAssistantImageExpression(minTurnIndex),
|
|
101
|
+
returnByValue: true,
|
|
102
|
+
});
|
|
103
|
+
const raw = Array.isArray(result?.value) ? result.value : [];
|
|
104
|
+
const normalized = raw
|
|
105
|
+
.map((item) => ({
|
|
106
|
+
url: typeof item?.url === "string" ? item.url : "",
|
|
107
|
+
alt: typeof item?.alt === "string" ? item.alt : undefined,
|
|
108
|
+
width: typeof item?.width === "number" ? item.width : undefined,
|
|
109
|
+
height: typeof item?.height === "number" ? item.height : undefined,
|
|
110
|
+
fileId: typeof item?.url === "string" ? extractFileId(item.url) : undefined,
|
|
111
|
+
}))
|
|
112
|
+
.filter((item) => item.url.length > 0);
|
|
113
|
+
return dedupeImages(normalized);
|
|
114
|
+
}
|
|
115
|
+
async function readAssistantGeneratedImagesWithFallback(Runtime, minTurnIndex) {
|
|
116
|
+
const filteredImages = await readAssistantGeneratedImages(Runtime, minTurnIndex ?? undefined).catch(() => []);
|
|
117
|
+
if (filteredImages.length > 0 ||
|
|
118
|
+
typeof minTurnIndex !== "number" ||
|
|
119
|
+
!Number.isFinite(minTurnIndex)) {
|
|
120
|
+
return filteredImages;
|
|
121
|
+
}
|
|
122
|
+
const [fallbackImages, fallbackSnapshot] = await Promise.all([
|
|
123
|
+
readAssistantGeneratedImages(Runtime).catch(() => []),
|
|
124
|
+
readAssistantSnapshot(Runtime).catch(() => null),
|
|
125
|
+
]);
|
|
126
|
+
const fallbackTurnIndex = typeof fallbackSnapshot?.turnIndex === "number" ? fallbackSnapshot.turnIndex : null;
|
|
127
|
+
const nearBoundary = fallbackTurnIndex !== null && fallbackTurnIndex + 1 >= Math.floor(minTurnIndex);
|
|
128
|
+
return fallbackImages.length > 0 && nearBoundary ? fallbackImages : [];
|
|
129
|
+
}
|
|
130
|
+
function resolveGeneratedImageWaitTimeoutMs(waitTimeoutMs) {
|
|
131
|
+
const requestedTimeout = typeof waitTimeoutMs === "number" && Number.isFinite(waitTimeoutMs)
|
|
132
|
+
? waitTimeoutMs
|
|
133
|
+
: GENERATED_IMAGE_WAIT_MAX_MS;
|
|
134
|
+
return Math.max(GENERATED_IMAGE_WAIT_MIN_MS, Math.min(requestedTimeout, GENERATED_IMAGE_WAIT_MAX_MS));
|
|
135
|
+
}
|
|
136
|
+
export function resolveGeneratedImageWaitTimeoutMsForTest(waitTimeoutMs) {
|
|
137
|
+
return resolveGeneratedImageWaitTimeoutMs(waitTimeoutMs);
|
|
138
|
+
}
|
|
139
|
+
function contentTypeToExtension(contentType) {
|
|
140
|
+
const value = String(contentType ?? "").toLowerCase();
|
|
141
|
+
if (value.includes("png"))
|
|
142
|
+
return "png";
|
|
143
|
+
if (value.includes("jpeg") || value.includes("jpg"))
|
|
144
|
+
return "jpg";
|
|
145
|
+
if (value.includes("webp"))
|
|
146
|
+
return "webp";
|
|
147
|
+
if (value.includes("gif"))
|
|
148
|
+
return "gif";
|
|
149
|
+
if (value.includes("svg"))
|
|
150
|
+
return "svg";
|
|
151
|
+
return "bin";
|
|
152
|
+
}
|
|
153
|
+
function resolveSiblingImagePath(basePath, index, extension) {
|
|
154
|
+
const ext = path.extname(basePath);
|
|
155
|
+
const dir = path.dirname(basePath);
|
|
156
|
+
const stem = ext ? path.basename(basePath, ext) : path.basename(basePath);
|
|
157
|
+
if (index === 0) {
|
|
158
|
+
return ext ? basePath : path.join(dir, `${stem}.${extension}`);
|
|
159
|
+
}
|
|
160
|
+
const suffix = ext ? `${stem}.${index + 1}${ext}` : `${stem}.${index + 1}.${extension}`;
|
|
161
|
+
return path.join(dir, suffix);
|
|
162
|
+
}
|
|
163
|
+
function sanitizeGeneratedImageStem(value) {
|
|
164
|
+
return value
|
|
165
|
+
.replace(/[^a-zA-Z0-9_-]+/g, "-")
|
|
166
|
+
.replace(/-+/g, "-")
|
|
167
|
+
.replace(/^-|-$/g, "")
|
|
168
|
+
.slice(0, 48);
|
|
169
|
+
}
|
|
170
|
+
function resolveDefaultGeneratedImagePath(images, sessionId) {
|
|
171
|
+
const primary = images[0];
|
|
172
|
+
const stemSource = primary?.fileId || primary?.alt || primary?.url || `generated-${Date.now().toString(36)}`;
|
|
173
|
+
const stem = sanitizeGeneratedImageStem(stemSource) || `generated-${Date.now().toString(36)}`;
|
|
174
|
+
const baseDir = sessionId
|
|
175
|
+
? resolveSessionArtifactsDir(sessionId)
|
|
176
|
+
: path.join(getOracleHomeDir(), ".temp");
|
|
177
|
+
return path.join(baseDir, `${stem}.png`);
|
|
178
|
+
}
|
|
179
|
+
async function buildCookieHeader(Network) {
|
|
180
|
+
const response = await Network.getCookies({ urls: ["https://chatgpt.com/"] });
|
|
181
|
+
return (response.cookies ?? [])
|
|
182
|
+
.filter((cookie) => cookie.name && typeof cookie.value === "string")
|
|
183
|
+
.map((cookie) => `${cookie.name}=${cookie.value}`)
|
|
184
|
+
.join("; ");
|
|
185
|
+
}
|
|
186
|
+
export async function saveChatGptGeneratedImages(params) {
|
|
187
|
+
const { Network, images, outputPath, logger } = params;
|
|
188
|
+
if (!images.length)
|
|
189
|
+
return { saved: false, imageCount: 0, savedImages: [], errors: [] };
|
|
190
|
+
const cookieHeader = await buildCookieHeader(Network);
|
|
191
|
+
if (!cookieHeader) {
|
|
192
|
+
return {
|
|
193
|
+
saved: false,
|
|
194
|
+
imageCount: images.length,
|
|
195
|
+
savedImages: [],
|
|
196
|
+
errors: ["Missing ChatGPT cookies for image download."],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const savedImages = [];
|
|
200
|
+
const errors = [];
|
|
201
|
+
await fs.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
202
|
+
for (let index = 0; index < images.length; index += 1) {
|
|
203
|
+
const image = images[index];
|
|
204
|
+
try {
|
|
205
|
+
const response = await fetch(image.url, {
|
|
206
|
+
headers: {
|
|
207
|
+
cookie: cookieHeader,
|
|
208
|
+
"user-agent": "Mozilla/5.0",
|
|
209
|
+
},
|
|
210
|
+
redirect: "follow",
|
|
211
|
+
});
|
|
212
|
+
if (!response.ok) {
|
|
213
|
+
throw new Error(`download failed: ${response.status} ${response.statusText}`);
|
|
214
|
+
}
|
|
215
|
+
const contentType = response.headers.get("content-type");
|
|
216
|
+
const extension = contentTypeToExtension(contentType);
|
|
217
|
+
const targetPath = resolveSiblingImagePath(path.resolve(outputPath), index, extension);
|
|
218
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
219
|
+
await fs.writeFile(targetPath, buffer);
|
|
220
|
+
savedImages.push({
|
|
221
|
+
kind: "image",
|
|
222
|
+
path: targetPath,
|
|
223
|
+
label: index === 0 ? "Generated image" : `Generated image ${index + 1}`,
|
|
224
|
+
mimeType: contentType ?? undefined,
|
|
225
|
+
sizeBytes: buffer.length,
|
|
226
|
+
sourceUrl: image.url,
|
|
227
|
+
url: image.url,
|
|
228
|
+
finalUrl: response.url,
|
|
229
|
+
alt: image.alt,
|
|
230
|
+
width: image.width,
|
|
231
|
+
height: image.height,
|
|
232
|
+
fileId: image.fileId,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
237
|
+
errors.push(`${image.fileId ?? image.url}: ${message}`);
|
|
238
|
+
logger?.(`[browser] Failed to save generated image ${index + 1}/${images.length}: ${message}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
saved: savedImages.length > 0,
|
|
243
|
+
imageCount: images.length,
|
|
244
|
+
savedImages,
|
|
245
|
+
errors,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
export async function collectGeneratedImageArtifacts(params) {
|
|
249
|
+
const explicitTargetPath = params.generateImagePath ?? params.outputPath;
|
|
250
|
+
let generatedImages = await readAssistantGeneratedImagesWithFallback(params.Runtime, params.minTurnIndex ?? undefined);
|
|
251
|
+
let latestAnswerText = params.answerText;
|
|
252
|
+
if (explicitTargetPath && generatedImages.length === 0) {
|
|
253
|
+
const deadline = Date.now() + resolveGeneratedImageWaitTimeoutMs(params.waitTimeoutMs);
|
|
254
|
+
while (Date.now() < deadline) {
|
|
255
|
+
await delay(1500);
|
|
256
|
+
generatedImages = await readAssistantGeneratedImagesWithFallback(params.Runtime, params.minTurnIndex ?? undefined);
|
|
257
|
+
if (generatedImages.length > 0) {
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
const latestSnapshot = await readAssistantSnapshot(params.Runtime, params.minTurnIndex ?? undefined).catch(() => null);
|
|
261
|
+
const snapshotText = typeof latestSnapshot?.text === "string" ? latestSnapshot.text.trim() : "";
|
|
262
|
+
if (snapshotText) {
|
|
263
|
+
latestAnswerText = snapshotText;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const imageCount = generatedImages.length;
|
|
268
|
+
if (explicitTargetPath && imageCount === 0) {
|
|
269
|
+
throw new Error(`No images generated. Response text:\n${latestAnswerText || "(empty response)"}`);
|
|
270
|
+
}
|
|
271
|
+
if (imageCount === 0) {
|
|
272
|
+
return {
|
|
273
|
+
generatedImages,
|
|
274
|
+
savedImages: [],
|
|
275
|
+
imageCount,
|
|
276
|
+
markdownSuffix: "",
|
|
277
|
+
answerText: latestAnswerText,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const targetPath = explicitTargetPath ?? resolveDefaultGeneratedImagePath(generatedImages, params.sessionId);
|
|
281
|
+
if (!explicitTargetPath) {
|
|
282
|
+
params.logger?.(`[browser] Auto-saving generated images to ${targetPath}`);
|
|
283
|
+
}
|
|
284
|
+
const saved = await saveChatGptGeneratedImages({
|
|
285
|
+
Network: params.Network,
|
|
286
|
+
images: generatedImages,
|
|
287
|
+
outputPath: targetPath,
|
|
288
|
+
logger: params.logger,
|
|
289
|
+
});
|
|
290
|
+
if (!saved.saved) {
|
|
291
|
+
const detail = saved.errors.length > 0 ? `\n${saved.errors.join("\n")}` : "";
|
|
292
|
+
if (explicitTargetPath) {
|
|
293
|
+
throw new Error(`No images generated. Response text:\n${latestAnswerText || "(empty response)"}${detail}`);
|
|
294
|
+
}
|
|
295
|
+
params.logger?.(`[browser] Auto-save for generated images failed; returning metadata only.${detail}`);
|
|
296
|
+
return {
|
|
297
|
+
generatedImages,
|
|
298
|
+
savedImages: [],
|
|
299
|
+
imageCount,
|
|
300
|
+
markdownSuffix: `\n\n*Generated ${imageCount} image(s).*`,
|
|
301
|
+
answerText: latestAnswerText,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const primaryPath = saved.savedImages[0]?.path ?? targetPath;
|
|
305
|
+
const suffix = saved.savedImages.length > 1
|
|
306
|
+
? `\n\n*Generated ${saved.imageCount} image(s). Saved ${saved.savedImages.length} file(s) starting at: ${primaryPath}*`
|
|
307
|
+
: `\n\n*Generated ${saved.imageCount} image(s). Saved to: ${primaryPath}*`;
|
|
308
|
+
return {
|
|
309
|
+
generatedImages,
|
|
310
|
+
savedImages: saved.savedImages,
|
|
311
|
+
imageCount: saved.imageCount,
|
|
312
|
+
markdownSuffix: suffix,
|
|
313
|
+
answerText: latestAnswerText,
|
|
314
|
+
};
|
|
315
|
+
}
|