@steipete/oracle 0.8.6 → 0.10.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.
Files changed (181) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +130 -45
  3. package/dist/bin/oracle-cli.js +613 -379
  4. package/dist/bin/oracle-mcp.js +2 -2
  5. package/dist/bin/oracle.js +165 -279
  6. package/dist/scripts/agent-send.js +31 -31
  7. package/dist/scripts/check.js +6 -6
  8. package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
  9. package/dist/scripts/docs-list.js +30 -30
  10. package/dist/scripts/git-policy.js +25 -23
  11. package/dist/scripts/run-cli.js +8 -8
  12. package/dist/scripts/runner.js +203 -195
  13. package/dist/scripts/test-browser.js +21 -18
  14. package/dist/scripts/test-remote-chrome.js +20 -20
  15. package/dist/src/bridge/connection.js +18 -18
  16. package/dist/src/bridge/userConfigFile.js +7 -7
  17. package/dist/src/browser/actions/assistantResponse.js +149 -101
  18. package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
  19. package/dist/src/browser/actions/attachments.js +246 -150
  20. package/dist/src/browser/actions/domEvents.js +2 -2
  21. package/dist/src/browser/actions/modelSelection.js +314 -104
  22. package/dist/src/browser/actions/navigation.js +161 -136
  23. package/dist/src/browser/actions/promptComposer.js +100 -64
  24. package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
  25. package/dist/src/browser/actions/thinkingTime.js +207 -110
  26. package/dist/src/browser/chromeLifecycle.js +62 -60
  27. package/dist/src/browser/config.js +34 -15
  28. package/dist/src/browser/constants.js +17 -12
  29. package/dist/src/browser/cookies.js +19 -19
  30. package/dist/src/browser/detect.js +62 -62
  31. package/dist/src/browser/domDebug.js +1 -1
  32. package/dist/src/browser/index.js +452 -303
  33. package/dist/src/browser/modelStrategy.js +1 -1
  34. package/dist/src/browser/pageActions.js +5 -5
  35. package/dist/src/browser/policies.js +16 -13
  36. package/dist/src/browser/profileState.js +44 -39
  37. package/dist/src/browser/prompt.js +72 -42
  38. package/dist/src/browser/promptSummary.js +5 -5
  39. package/dist/src/browser/providerDomFlow.js +17 -0
  40. package/dist/src/browser/providers/chatgptDomProvider.js +49 -0
  41. package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +254 -0
  42. package/dist/src/browser/providers/index.js +2 -0
  43. package/dist/src/browser/reattach.js +67 -34
  44. package/dist/src/browser/reattachHelpers.js +31 -26
  45. package/dist/src/browser/sessionRunner.js +37 -25
  46. package/dist/src/browser/utils.js +9 -9
  47. package/dist/src/browserMode.js +1 -1
  48. package/dist/src/cli/bridge/claudeConfig.js +16 -16
  49. package/dist/src/cli/bridge/client.js +28 -20
  50. package/dist/src/cli/bridge/codexConfig.js +16 -16
  51. package/dist/src/cli/bridge/doctor.js +47 -39
  52. package/dist/src/cli/bridge/host.js +58 -56
  53. package/dist/src/cli/browserConfig.js +65 -45
  54. package/dist/src/cli/browserDefaults.js +27 -26
  55. package/dist/src/cli/bundleWarnings.js +1 -1
  56. package/dist/src/cli/clipboard.js +11 -2
  57. package/dist/src/cli/detach.js +7 -4
  58. package/dist/src/cli/dryRun.js +29 -25
  59. package/dist/src/cli/duplicatePromptGuard.js +3 -3
  60. package/dist/src/cli/engine.js +9 -9
  61. package/dist/src/cli/errorUtils.js +1 -1
  62. package/dist/src/cli/fileSize.js +11 -0
  63. package/dist/src/cli/format.js +2 -2
  64. package/dist/src/cli/help.js +28 -28
  65. package/dist/src/cli/hiddenAliases.js +3 -3
  66. package/dist/src/cli/markdownBundle.js +12 -8
  67. package/dist/src/cli/markdownRenderer.js +15 -15
  68. package/dist/src/cli/notifier.js +77 -67
  69. package/dist/src/cli/options.js +145 -87
  70. package/dist/src/cli/oscUtils.js +1 -1
  71. package/dist/src/cli/promptRequirement.js +2 -2
  72. package/dist/src/cli/renderOutput.js +1 -1
  73. package/dist/src/cli/rootAlias.js +1 -1
  74. package/dist/src/cli/runOptions.js +37 -25
  75. package/dist/src/cli/sessionCommand.js +31 -21
  76. package/dist/src/cli/sessionDisplay.js +182 -79
  77. package/dist/src/cli/sessionLineage.js +60 -0
  78. package/dist/src/cli/sessionRunner.js +118 -90
  79. package/dist/src/cli/sessionTable.js +28 -24
  80. package/dist/src/cli/stdin.js +22 -0
  81. package/dist/src/cli/tagline.js +121 -124
  82. package/dist/src/cli/tui/index.js +140 -127
  83. package/dist/src/cli/writeOutputPath.js +5 -5
  84. package/dist/src/config.js +7 -7
  85. package/dist/src/gemini-web/browserSessionManager.js +80 -0
  86. package/dist/src/gemini-web/client.js +81 -64
  87. package/dist/src/gemini-web/executionMode.js +16 -0
  88. package/dist/src/gemini-web/executor.js +327 -169
  89. package/dist/src/gemini-web/index.js +1 -1
  90. package/dist/src/mcp/server.js +16 -12
  91. package/dist/src/mcp/tools/consult.js +81 -64
  92. package/dist/src/mcp/tools/sessionResources.js +12 -12
  93. package/dist/src/mcp/tools/sessions.js +26 -17
  94. package/dist/src/mcp/types.js +5 -5
  95. package/dist/src/mcp/utils.js +15 -7
  96. package/dist/src/oracle/background.js +15 -15
  97. package/dist/src/oracle/claude.js +53 -25
  98. package/dist/src/oracle/client.js +84 -46
  99. package/dist/src/oracle/config.js +124 -58
  100. package/dist/src/oracle/errors.js +38 -38
  101. package/dist/src/oracle/files.js +69 -45
  102. package/dist/src/oracle/finishLine.js +10 -8
  103. package/dist/src/oracle/format.js +3 -3
  104. package/dist/src/oracle/gemini.js +37 -30
  105. package/dist/src/oracle/logging.js +7 -7
  106. package/dist/src/oracle/markdown.js +28 -28
  107. package/dist/src/oracle/modelResolver.js +16 -16
  108. package/dist/src/oracle/multiModelRunner.js +12 -12
  109. package/dist/src/oracle/oscProgress.js +8 -8
  110. package/dist/src/oracle/promptAssembly.js +6 -3
  111. package/dist/src/oracle/request.js +23 -15
  112. package/dist/src/oracle/run.js +172 -140
  113. package/dist/src/oracle/runUtils.js +8 -5
  114. package/dist/src/oracle/tokenEstimate.js +6 -6
  115. package/dist/src/oracle/tokenStats.js +5 -5
  116. package/dist/src/oracle/tokenStringifier.js +5 -5
  117. package/dist/src/oracle.js +12 -12
  118. package/dist/src/oracleHome.js +3 -3
  119. package/dist/src/remote/client.js +25 -25
  120. package/dist/src/remote/health.js +20 -20
  121. package/dist/src/remote/remoteServiceConfig.js +9 -9
  122. package/dist/src/remote/server.js +129 -118
  123. package/dist/src/sessionManager.js +81 -75
  124. package/dist/src/sessionStore.js +3 -3
  125. package/dist/src/version.js +10 -10
  126. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  127. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  128. package/dist/vendor/oracle-notifier/README.md +2 -0
  129. package/package.json +69 -65
  130. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  131. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  132. package/vendor/oracle-notifier/README.md +2 -0
  133. package/dist/markdansi/types/index.js +0 -4
  134. package/dist/oracle/bin/oracle-cli.js +0 -472
  135. package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
  136. package/dist/oracle/src/browser/actions/attachments.js +0 -82
  137. package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
  138. package/dist/oracle/src/browser/actions/navigation.js +0 -75
  139. package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
  140. package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
  141. package/dist/oracle/src/browser/config.js +0 -33
  142. package/dist/oracle/src/browser/constants.js +0 -40
  143. package/dist/oracle/src/browser/cookies.js +0 -210
  144. package/dist/oracle/src/browser/domDebug.js +0 -36
  145. package/dist/oracle/src/browser/index.js +0 -331
  146. package/dist/oracle/src/browser/pageActions.js +0 -5
  147. package/dist/oracle/src/browser/prompt.js +0 -88
  148. package/dist/oracle/src/browser/promptSummary.js +0 -20
  149. package/dist/oracle/src/browser/sessionRunner.js +0 -80
  150. package/dist/oracle/src/browser/utils.js +0 -62
  151. package/dist/oracle/src/browserMode.js +0 -1
  152. package/dist/oracle/src/cli/browserConfig.js +0 -44
  153. package/dist/oracle/src/cli/dryRun.js +0 -59
  154. package/dist/oracle/src/cli/engine.js +0 -17
  155. package/dist/oracle/src/cli/errorUtils.js +0 -9
  156. package/dist/oracle/src/cli/help.js +0 -70
  157. package/dist/oracle/src/cli/markdownRenderer.js +0 -15
  158. package/dist/oracle/src/cli/options.js +0 -103
  159. package/dist/oracle/src/cli/promptRequirement.js +0 -14
  160. package/dist/oracle/src/cli/rootAlias.js +0 -30
  161. package/dist/oracle/src/cli/sessionCommand.js +0 -77
  162. package/dist/oracle/src/cli/sessionDisplay.js +0 -270
  163. package/dist/oracle/src/cli/sessionRunner.js +0 -94
  164. package/dist/oracle/src/heartbeat.js +0 -43
  165. package/dist/oracle/src/oracle/client.js +0 -48
  166. package/dist/oracle/src/oracle/config.js +0 -29
  167. package/dist/oracle/src/oracle/errors.js +0 -101
  168. package/dist/oracle/src/oracle/files.js +0 -220
  169. package/dist/oracle/src/oracle/format.js +0 -33
  170. package/dist/oracle/src/oracle/fsAdapter.js +0 -7
  171. package/dist/oracle/src/oracle/oscProgress.js +0 -60
  172. package/dist/oracle/src/oracle/request.js +0 -48
  173. package/dist/oracle/src/oracle/run.js +0 -444
  174. package/dist/oracle/src/oracle/tokenStats.js +0 -39
  175. package/dist/oracle/src/oracle/types.js +0 -1
  176. package/dist/oracle/src/oracle.js +0 -9
  177. package/dist/oracle/src/sessionManager.js +0 -205
  178. package/dist/oracle/src/version.js +0 -39
  179. package/dist/scripts/chrome/browser-tools.js +0 -295
  180. package/dist/src/browser/profileSync.js +0 -141
  181. /package/dist/{oracle/src/browser/types.js → src/gemini-web/executionClients.js} +0 -0
@@ -1,28 +1,39 @@
1
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
- import path from 'node:path';
3
- const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
4
- const MODEL_HEADER_NAME = 'x-goog-ext-525001261-jspb';
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
4
+ const MODEL_HEADER_NAME = "x-goog-ext-525001261-jspb";
5
5
  const MODEL_HEADERS = {
6
- 'gemini-3-pro': '[1,null,null,null,"9d8ca3786ebdfbea",null,null,0,[4]]',
7
- 'gemini-2.5-pro': '[1,null,null,null,"4af6c7f5da75d65d",null,null,0,[4]]',
8
- 'gemini-2.5-flash': '[1,null,null,null,"9ec249fc9ad08861",null,null,0,[4]]',
6
+ "gemini-3-pro": '[1,null,null,null,"9d8ca3786ebdfbea",null,null,0,[4]]',
7
+ "gemini-3-pro-deep-think": '[1,null,null,null,"e6fa609c3fa255c0",null,null,0,[4],null,null,3]',
8
+ "gemini-2.5-pro": '[1,null,null,null,"4af6c7f5da75d65d",null,null,0,[4]]',
9
+ "gemini-2.5-flash": '[1,null,null,null,"9ec249fc9ad08861",null,null,0,[4]]',
10
+ };
11
+ const GEMINI_APP_URL = "https://gemini.google.com/app";
12
+ const GEMINI_STREAM_GENERATE_URL = "https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate";
13
+ const GEMINI_UPLOAD_URL = "https://content-push.googleapis.com/upload";
14
+ const GEMINI_UPLOAD_PUSH_ID = "feeds/mcudyrk2a4khkz";
15
+ const GEMINI_UPLOAD_MIME_TYPES = {
16
+ ".bmp": "image/bmp",
17
+ ".gif": "image/gif",
18
+ ".jpeg": "image/jpeg",
19
+ ".jpg": "image/jpeg",
20
+ ".pdf": "application/pdf",
21
+ ".png": "image/png",
22
+ ".svg": "image/svg+xml",
23
+ ".webp": "image/webp",
9
24
  };
10
- const GEMINI_APP_URL = 'https://gemini.google.com/app';
11
- const GEMINI_STREAM_GENERATE_URL = 'https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate';
12
- const GEMINI_UPLOAD_URL = 'https://content-push.googleapis.com/upload';
13
- const GEMINI_UPLOAD_PUSH_ID = 'feeds/mcudyrk2a4khkz';
14
25
  function getNestedValue(value, pathParts, fallback) {
15
26
  let current = value;
16
27
  for (const part of pathParts) {
17
28
  if (current == null)
18
29
  return fallback;
19
- if (typeof part === 'number') {
30
+ if (typeof part === "number") {
20
31
  if (!Array.isArray(current))
21
32
  return fallback;
22
33
  current = current[part];
23
34
  }
24
35
  else {
25
- if (typeof current !== 'object')
36
+ if (typeof current !== "object")
26
37
  return fallback;
27
38
  current = current[part];
28
39
  }
@@ -31,40 +42,40 @@ function getNestedValue(value, pathParts, fallback) {
31
42
  }
32
43
  function buildCookieHeader(cookieMap) {
33
44
  return Object.entries(cookieMap)
34
- .filter(([, value]) => typeof value === 'string' && value.length > 0)
45
+ .filter(([, value]) => typeof value === "string" && value.length > 0)
35
46
  .map(([name, value]) => `${name}=${value}`)
36
- .join('; ');
47
+ .join("; ");
37
48
  }
38
49
  export async function fetchGeminiAccessToken(cookieMap, signal) {
39
50
  const cookieHeader = buildCookieHeader(cookieMap);
40
51
  const res = await fetch(GEMINI_APP_URL, {
41
- redirect: 'follow',
52
+ redirect: "follow",
42
53
  signal,
43
54
  headers: {
44
55
  cookie: cookieHeader,
45
- 'user-agent': USER_AGENT,
56
+ "user-agent": USER_AGENT,
46
57
  },
47
58
  });
48
59
  const html = await res.text();
49
- const tokens = ['SNlM0e', 'thykhd'];
60
+ const tokens = ["SNlM0e", "thykhd"];
50
61
  for (const key of tokens) {
51
62
  const match = html.match(new RegExp(`"${key}":"(.*?)"`));
52
63
  if (match?.[1])
53
64
  return match[1];
54
65
  }
55
- throw new Error('Unable to locate Gemini access token on gemini.google.com/app (missing SNlM0e/thykhd).');
66
+ throw new Error("Unable to locate Gemini access token on gemini.google.com/app (missing SNlM0e/thykhd).");
56
67
  }
57
68
  function trimGeminiJsonEnvelope(text) {
58
- const start = text.indexOf('[');
59
- const end = text.lastIndexOf(']');
69
+ const start = text.indexOf("[");
70
+ const end = text.lastIndexOf("]");
60
71
  if (start === -1 || end === -1 || end <= start) {
61
- throw new Error('Gemini response did not contain a JSON payload.');
72
+ throw new Error("Gemini response did not contain a JSON payload.");
62
73
  }
63
74
  return text.slice(start, end + 1);
64
75
  }
65
76
  function extractErrorCode(responseJson) {
66
77
  const code = getNestedValue(responseJson, [0, 5, 2, 0, 1, 0], -1);
67
- return typeof code === 'number' && code >= 0 ? code : undefined;
78
+ return typeof code === "number" && code >= 0 ? code : undefined;
68
79
  }
69
80
  function extractGgdlUrls(rawText) {
70
81
  const matches = rawText.match(/https:\/\/lh3\.googleusercontent\.com\/gg-dl\/[^\s"']+/g) ?? [];
@@ -79,18 +90,18 @@ function extractGgdlUrls(rawText) {
79
90
  return urls;
80
91
  }
81
92
  function ensureFullSizeImageUrl(url) {
82
- if (url.includes('=s2048'))
93
+ if (url.includes("=s2048"))
83
94
  return url;
84
- if (url.includes('=s'))
95
+ if (url.includes("=s"))
85
96
  return url;
86
97
  return `${url}=s2048`;
87
98
  }
88
99
  async function fetchWithCookiePreservingRedirects(url, init, signal, maxRedirects = 10) {
89
100
  let current = url;
90
101
  for (let i = 0; i <= maxRedirects; i += 1) {
91
- const res = await fetch(current, { ...init, redirect: 'manual', signal });
102
+ const res = await fetch(current, { ...init, redirect: "manual", signal });
92
103
  if (res.status >= 300 && res.status < 400) {
93
- const location = res.headers.get('location');
104
+ const location = res.headers.get("location");
94
105
  if (!location)
95
106
  return res;
96
107
  current = new URL(location, current).toString();
@@ -105,7 +116,7 @@ async function downloadGeminiImage(url, cookieMap, outputPath, signal) {
105
116
  const res = await fetchWithCookiePreservingRedirects(ensureFullSizeImageUrl(url), {
106
117
  headers: {
107
118
  cookie: cookieHeader,
108
- 'user-agent': USER_AGENT,
119
+ "user-agent": USER_AGENT,
109
120
  },
110
121
  }, signal);
111
122
  if (!res.ok) {
@@ -119,15 +130,16 @@ async function uploadGeminiFile(filePath, signal) {
119
130
  const absPath = path.resolve(process.cwd(), filePath);
120
131
  const data = await readFile(absPath);
121
132
  const fileName = path.basename(absPath);
133
+ const mimeType = GEMINI_UPLOAD_MIME_TYPES[path.extname(absPath).toLowerCase()] ?? "application/octet-stream";
122
134
  const form = new FormData();
123
- form.append('file', new Blob([data]), fileName);
135
+ form.append("file", new Blob([data], { type: mimeType }), fileName);
124
136
  const res = await fetch(GEMINI_UPLOAD_URL, {
125
- method: 'POST',
126
- redirect: 'follow',
137
+ method: "POST",
138
+ redirect: "follow",
127
139
  signal,
128
140
  headers: {
129
- 'push-id': GEMINI_UPLOAD_PUSH_ID,
130
- 'user-agent': USER_AGENT,
141
+ "push-id": GEMINI_UPLOAD_PUSH_ID,
142
+ "user-agent": USER_AGENT,
131
143
  },
132
144
  body: form,
133
145
  });
@@ -135,7 +147,7 @@ async function uploadGeminiFile(filePath, signal) {
135
147
  if (!res.ok) {
136
148
  throw new Error(`File upload failed: ${res.status} ${res.statusText} (${text.slice(0, 200)})`);
137
149
  }
138
- return { id: text, name: fileName };
150
+ return { id: text, name: fileName, mimeType };
139
151
  }
140
152
  function buildGeminiFReqPayload(prompt, uploaded, chatMetadata) {
141
153
  const promptPayload = uploaded.length > 0
@@ -143,9 +155,8 @@ function buildGeminiFReqPayload(prompt, uploaded, chatMetadata) {
143
155
  prompt,
144
156
  0,
145
157
  null,
146
- // Matches gemini-webapi payload format: [[[fileId, 1]]] for a single attachment.
147
- // Keep it extensible for multiple uploads by emitting one [[id, 1]] entry per file.
148
- uploaded.map((file) => [[file.id, 1]]),
158
+ // Format: [[[fileId, 1, null, "mimeType"], "filename", ...]]
159
+ uploaded.map((file) => [[file.id, 1, null, file.mimeType], file.name]),
149
160
  ]
150
161
  : [prompt];
151
162
  const innerList = [promptPayload, null, chatMetadata ?? null];
@@ -165,9 +176,15 @@ export function parseGeminiStreamGenerateResponse(rawText) {
165
176
  const parsed = JSON.parse(partBody);
166
177
  const candidateList = getNestedValue(parsed, [4], []);
167
178
  if (Array.isArray(candidateList) && candidateList.length > 0) {
168
- bodyIndex = i;
169
- body = parsed;
170
- break;
179
+ const candidateText = getNestedValue(candidateList[0], [1, 0], "");
180
+ const hasText = typeof candidateText === "string" && candidateText.length > 0;
181
+ if (body === null) {
182
+ bodyIndex = i;
183
+ body = parsed;
184
+ }
185
+ else if (hasText) {
186
+ body = parsed;
187
+ }
171
188
  }
172
189
  }
173
190
  catch {
@@ -176,7 +193,7 @@ export function parseGeminiStreamGenerateResponse(rawText) {
176
193
  }
177
194
  const candidateList = getNestedValue(body, [4], []);
178
195
  const firstCandidate = candidateList[0];
179
- const textRaw = getNestedValue(firstCandidate, [1, 0], '');
196
+ const textRaw = getNestedValue(firstCandidate, [1, 0], "");
180
197
  const cardContent = /^http:\/\/googleusercontent\.com\/card_content\/\d+/.test(textRaw);
181
198
  const text = cardContent
182
199
  ? (getNestedValue(firstCandidate, [22, 0], null) ?? textRaw)
@@ -190,7 +207,7 @@ export function parseGeminiStreamGenerateResponse(rawText) {
190
207
  if (!url)
191
208
  continue;
192
209
  images.push({
193
- kind: 'web',
210
+ kind: "web",
194
211
  url,
195
212
  title: getNestedValue(webImage, [7, 0], undefined),
196
213
  alt: getNestedValue(webImage, [0, 4], undefined),
@@ -222,10 +239,10 @@ export function parseGeminiStreamGenerateResponse(rawText) {
222
239
  if (!url)
223
240
  continue;
224
241
  images.push({
225
- kind: 'generated',
242
+ kind: "generated",
226
243
  url,
227
- title: '[Generated Image]',
228
- alt: '',
244
+ title: "[Generated Image]",
245
+ alt: "",
229
246
  });
230
247
  }
231
248
  }
@@ -240,24 +257,24 @@ export async function runGeminiWebOnce(input) {
240
257
  const uploaded = [];
241
258
  for (const file of input.files ?? []) {
242
259
  if (input.signal?.aborted) {
243
- throw new Error('Gemini web run aborted before upload.');
260
+ throw new Error("Gemini web run aborted before upload.");
244
261
  }
245
262
  uploaded.push(await uploadGeminiFile(file, input.signal));
246
263
  }
247
264
  const fReq = buildGeminiFReqPayload(input.prompt, uploaded, input.chatMetadata ?? null);
248
265
  const params = new URLSearchParams();
249
- params.set('at', at);
250
- params.set('f.req', fReq);
266
+ params.set("at", at);
267
+ params.set("f.req", fReq);
251
268
  const res = await fetch(GEMINI_STREAM_GENERATE_URL, {
252
- method: 'POST',
253
- redirect: 'follow',
269
+ method: "POST",
270
+ redirect: "follow",
254
271
  signal: input.signal,
255
272
  headers: {
256
- 'content-type': 'application/x-www-form-urlencoded;charset=utf-8',
257
- origin: 'https://gemini.google.com',
258
- referer: 'https://gemini.google.com/',
259
- 'x-same-domain': '1',
260
- 'user-agent': USER_AGENT,
273
+ "content-type": "application/x-www-form-urlencoded;charset=utf-8",
274
+ origin: "https://gemini.google.com",
275
+ referer: "https://gemini.google.com/",
276
+ "x-same-domain": "1",
277
+ "user-agent": USER_AGENT,
261
278
  cookie: cookieHeader,
262
279
  [MODEL_HEADER_NAME]: MODEL_HEADERS[input.model],
263
280
  },
@@ -267,7 +284,7 @@ export async function runGeminiWebOnce(input) {
267
284
  if (!res.ok) {
268
285
  return {
269
286
  rawResponseText,
270
- text: '',
287
+ text: "",
271
288
  thoughts: null,
272
289
  metadata: input.chatMetadata ?? null,
273
290
  images: [],
@@ -278,7 +295,7 @@ export async function runGeminiWebOnce(input) {
278
295
  const parsed = parseGeminiStreamGenerateResponse(rawResponseText);
279
296
  return {
280
297
  rawResponseText,
281
- text: parsed.text ?? '',
298
+ text: parsed.text ?? "",
282
299
  thoughts: parsed.thoughts,
283
300
  metadata: parsed.metadata,
284
301
  images: parsed.images,
@@ -296,25 +313,25 @@ export async function runGeminiWebOnce(input) {
296
313
  const errorCode = extractErrorCode(responseJson);
297
314
  return {
298
315
  rawResponseText,
299
- text: '',
316
+ text: "",
300
317
  thoughts: null,
301
318
  metadata: input.chatMetadata ?? null,
302
319
  images: [],
303
- errorCode: typeof errorCode === 'number' ? errorCode : undefined,
304
- errorMessage: error instanceof Error ? error.message : String(error ?? ''),
320
+ errorCode: typeof errorCode === "number" ? errorCode : undefined,
321
+ errorMessage: error instanceof Error ? error.message : String(error ?? ""),
305
322
  };
306
323
  }
307
324
  }
308
325
  export async function runGeminiWebWithFallback(input) {
309
326
  const attempt = await runGeminiWebOnce(input);
310
- if (isGeminiModelUnavailable(attempt.errorCode) && input.model !== 'gemini-2.5-flash') {
311
- const fallback = await runGeminiWebOnce({ ...input, model: 'gemini-2.5-flash' });
312
- return { ...fallback, effectiveModel: 'gemini-2.5-flash' };
327
+ if (isGeminiModelUnavailable(attempt.errorCode) && input.model !== "gemini-2.5-flash") {
328
+ const fallback = await runGeminiWebOnce({ ...input, model: "gemini-2.5-flash" });
329
+ return { ...fallback, effectiveModel: "gemini-2.5-flash" };
313
330
  }
314
331
  return { ...attempt, effectiveModel: input.model };
315
332
  }
316
333
  export async function saveFirstGeminiImageFromOutput(output, cookieMap, outputPath, signal) {
317
- const generatedOrWeb = output.images.find((img) => img.kind === 'generated') ?? output.images[0];
334
+ const generatedOrWeb = output.images.find((img) => img.kind === "generated") ?? output.images[0];
318
335
  if (generatedOrWeb?.url) {
319
336
  await downloadGeminiImage(generatedOrWeb.url, cookieMap, outputPath, signal);
320
337
  return { saved: true, imageCount: output.images.length };
@@ -0,0 +1,16 @@
1
+ export function selectGeminiExecutionMode(input) {
2
+ const reasons = [];
3
+ if (input.model !== "gemini-3-pro-deep-think") {
4
+ return { mode: "http", reasons: ["model"] };
5
+ }
6
+ if (input.attachmentPaths.length > 0) {
7
+ reasons.push("attachments");
8
+ }
9
+ if (input.generateImagePath) {
10
+ reasons.push("image-generation");
11
+ }
12
+ if (input.editImagePath) {
13
+ reasons.push("image-edit");
14
+ }
15
+ return reasons.length === 0 ? { mode: "dom", reasons: [] } : { mode: "http", reasons };
16
+ }