gemini-reverse 1.0.2 → 1.0.4

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/client.js CHANGED
@@ -1,19 +1,32 @@
1
1
  'use strict';
2
2
 
3
3
  const axios = require('axios');
4
+ const crypto = require('crypto');
4
5
  const { v4: uuidv4 } = require('uuid');
5
- const { Endpoint, GRPC, Headers, Model, ErrorCode, TEMPORARY_CHAT_FLAG_INDEX } = require('./constants');
6
- const { APIError, GeminiError, TimeoutError, UsageLimitExceeded, ModelInvalid, TemporarilyBlocked } = require('./exceptions');
6
+ const {
7
+ Endpoint, GRPC, Headers, Model, AccountStatus, ErrorCode,
8
+ TEMPORARY_CHAT_FLAG_INDEX, STREAMING_FLAG_INDEX, GEM_FLAG_INDEX,
9
+ CARD_CONTENT_RE, ARTIFACTS_RE, DEFAULT_METADATA, MODEL_HEADER_KEY,
10
+ } = require('./constants');
11
+ const {
12
+ APIError, GeminiError, TimeoutError, UsageLimitExceeded, ModelInvalid, TemporarilyBlocked,
13
+ } = require('./exceptions');
7
14
  const { ChatMixin } = require('./components/chatMixin');
8
15
  const { GemMixin } = require('./components/gemMixin');
16
+ const { ResearchMixin } = require('./components/researchMixin');
17
+ const { AvailableModel } = require('./types/availablemodel');
9
18
  const { Candidate } = require('./types/candidate');
10
19
  const { ModelOutput } = require('./types/modeloutput');
11
20
  const { WebImage, GeneratedImage } = require('./types/image');
21
+ const { GeneratedVideo, GeneratedMedia } = require('./types/video');
22
+ const { DeepResearchPlan } = require('./types/research');
12
23
  const { RPCData } = require('./types/grpc');
24
+ const { Gem } = require('./types/gem');
13
25
  const { getAccessToken, cookieStr, parseCookies, parseProxy } = require('./utils/accessToken');
14
26
  const { rotate1psidts } = require('./utils/rotate');
15
27
  const { uploadFile, parseFileName } = require('./utils/upload');
16
28
  const { getDeltaByFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse } = require('./utils/parsing');
29
+ const { extractDeepResearchPlan } = require('./utils/research');
17
30
 
18
31
  function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
19
32
 
@@ -21,11 +34,17 @@ function applyMixins(Base, ...mixins) {
21
34
  for (const mixin of mixins) {
22
35
  for (const key of Object.getOwnPropertyNames(mixin.prototype)) {
23
36
  if (key === 'constructor') continue;
24
- Object.defineProperty(Base.prototype, key, Object.getOwnPropertyDescriptor(mixin.prototype, key));
37
+ Object.defineProperty(
38
+ Base.prototype,
39
+ key,
40
+ Object.getOwnPropertyDescriptor(mixin.prototype, key),
41
+ );
25
42
  }
26
43
  }
27
44
  }
28
45
 
46
+ const BARD_SETTINGS_PAYLOAD = '[[["adaptive_device_responses_enabled","advanced_mode_theme_override_triggered","advanced_zs_upsell_dismissal_count","advanced_zs_upsell_last_dismissed","ai_transparency_notice_dismissed","audio_overview_discovery_dismissal_count","audio_overview_discovery_last_dismissed","bard_in_chrome_link_sharing_enabled","bard_sticky_mode_disabled_count","canvas_create_discovery_tooltip_seen_count","combined_files_button_tag_seen_count","indigo_banner_explicit_dismissal_count","indigo_banner_impression_count","indigo_banner_last_seen_sec","current_popup_id","deep_research_has_seen_file_upload_tooltip","deep_research_model_update_disclaimer_display_count","default_bot_id","disabled_discovery_card_feature_ids","disabled_model_discovery_tooltip_feature_ids","disabled_mode_disclaimers","disabled_new_model_badge_mode_ids","disabled_settings_discovery_tooltip_feature_ids","disablement_disclaimer_last_dismissed_sec","disable_advanced_beta_dialog","disable_advanced_beta_non_en_banner","disable_advanced_resubscribe_ui","disable_at_mentions_discovery_tooltip","disable_autorun_fact_check_u18","disable_bot_create_tips_card","disable_bot_docs_in_gems_disclaimer","disable_bot_onboarding_dialog","disable_bot_save_reminder_tips_card","disable_bot_send_prompt_tips_card","disable_bot_shared_in_drive_disclaimer","disable_bot_try_create_tips_card","disable_colab_tooltip","disable_collapsed_tool_menu_tooltip","disable_continue_discovery_tooltip","disable_debug_info_moved_tooltip_v2","disable_enterprise_mode_dialog","disable_export_python_tooltip","disable_extensions_discovery_dialog","disable_extension_one_time_badge","disable_fact_check_tooltip_v2","disable_free_file_upload_tips_card","disable_generated_image_download_dialog","disable_get_app_banner","disable_get_app_desktop_dialog","disable_googler_in_enterprise_mode","disable_human_review_disclosure","disable_ice_open_vega_editor_tooltip","disable_image_upload_tooltip","disable_legal_concern_tooltip","disable_llm_history_import_disclaimer","disable_location_popup","disable_memory_discovery","disable_memory_extraction_discovery","disable_new_conversation_dialog","disable_onboarding_experience","disable_personal_context_tooltip","disable_photos_upload_disclaimer","disable_power_up_intro_tooltip","disable_scheduled_actions_mobile_notification_snackbar","disable_storybook_listen_button_tooltip","disable_streaming_settings_tooltip","disable_take_control_disclaimer","disable_teens_only_english_language_dialog","disable_tier1_rebranding_tooltip","disable_try_advanced_mode_dialog","enable_advanced_beta_mode","enable_advanced_mode","enable_googler_in_enterprise_mode","enable_memory","enable_memory_extraction","enable_personal_context","enable_personal_context_gemini","enable_personal_context_gemini_using_photos","enable_personal_context_gemini_using_workspace","enable_personal_context_search","enable_personal_context_youtube","enable_token_streaming","enforce_default_to_fast_version","mayo_discovery_banner_dismissal_count","mayo_discovery_banner_last_dismissed_sec","gempix_discovery_banner_dismissal_count","gempix_discovery_banner_last_dismissed","get_app_banner_ack_count","get_app_banner_seen_count","get_app_mobile_dialog_ack_count","guided_learning_banner_dismissal_count","guided_learning_banner_last_dismissed","has_accepted_agent_mode_fre_disclaimer","has_received_streaming_response","has_seen_agent_mode_tooltip","has_seen_bespoke_tooltip","has_seen_deepthink_mustard_tooltip","has_seen_deepthink_v2_tooltip","has_seen_deep_think_tooltip","has_seen_first_youtube_video_disclaimer","has_seen_ggo_tooltip","has_seen_image_grams_discovery_banner","has_seen_image_preview_in_input_area_tooltip","has_seen_kallo_discovery_banner","has_seen_kallo_tooltip","has_seen_model_picker_in_input_area_tooltip","has_seen_model_tooltip_in_input_area_for_gempix","has_seen_redo_with_gempix2_tooltip","has_seen_veograms_discovery_banner","has_seen_video_generation_discovery_banner","is_imported_chats_panel_open_by_default","jumpstart_onboarding_dismissal_count","last_dismissed_deep_research_implicit_invite","last_dismissed_discovery_feature_implicit_invites","last_dismissed_immersives_canvas_implicit_invite","last_dismissed_immersive_share_disclaimer_sec","last_dismissed_strike_timestamp_sec","last_dismissed_zs_student_aip_banner_sec","last_get_app_banner_ack_timestamp_sec","last_get_app_mobile_dialog_ack_timestamp_sec","last_human_review_disclosure_ack","last_selected_mode_id_in_embedded","last_selected_mode_id_on_web","last_two_up_activation_timestamp_sec","last_winter_olympics_interaction_timestamp_sec","memory_extracted_greeting_name","mini_gemini_tos_closed","mode_switcher_soft_badge_disabled_ids","mode_switcher_soft_badge_seen_count","personalization_first_party_onboarding_cross_surface_clicked","personalization_first_party_onboarding_cross_surface_seen_count","personalization_one_p_discovery_card_seen_count","personalization_one_p_discovery_last_consented","personalization_zero_state_card_last_interacted","personalization_zero_state_card_seen_count","popup_zs_visits_cooldown","require_reconsent_setting_for_personalization_banner_seen_count","show_debug_info","side_nav_open_by_default","student_verification_dismissal_count","student_verification_last_dismissed","task_viewer_cc_banner_dismissed_count","task_viewer_cc_banner_dismissed_time_sec","tool_menu_new_badge_disabled_ids","tool_menu_new_badge_impression_counts","tool_menu_soft_badge_disabled_ids","tool_menu_soft_badge_impression_counts","upload_disclaimer_last_consent_time_sec","viewed_student_aip_upsell_campaign_ids","voice_language","voice_name","web_and_app_activity_enabled","wellbeing_nudge_notice_last_dismissed_sec","zs_student_aip_banner_dismissal_count"]]]';
47
+
29
48
  class GeminiClient {
30
49
  constructor({ secure_1psid = null, secure_1psidts = null, proxy = null, cookies = {} } = {}) {
31
50
  this.cookies = { ...cookies };
@@ -34,6 +53,9 @@ class GeminiClient {
34
53
  this.accessToken = null;
35
54
  this.buildLabel = null;
36
55
  this.sessionId = null;
56
+ this.language = 'en';
57
+ this.pushId = 'feeds/mcudyrk2a4khkz';
58
+ this.accountStatus = AccountStatus.AVAILABLE;
37
59
  this.timeout = 300000;
38
60
  this.autoClose = false;
39
61
  this.closeDelay = 300000;
@@ -41,9 +63,12 @@ class GeminiClient {
41
63
  this.autoRefresh = true;
42
64
  this.refreshInterval = 540000;
43
65
  this.refreshTask = null;
44
- this.verbose = true;
45
66
  this.watchdogTimeout = 30000;
67
+ this.verbose = false;
68
+ this._running = false;
46
69
  this._reqid = Math.floor(Math.random() * 90000) + 10000;
70
+ this._modelRegistry = {};
71
+ this._recentChats = null;
47
72
  this._gems = null;
48
73
 
49
74
  if (secure_1psid) {
@@ -52,27 +77,46 @@ class GeminiClient {
52
77
  }
53
78
  }
54
79
 
55
- async init({ timeout = 300000, autoClose = false, closeDelay = 300000, autoRefresh = true, refreshInterval = 540000, verbose = true, watchdogTimeout = 30000 } = {}) {
80
+ async init({
81
+ timeout = 300000,
82
+ autoClose = false,
83
+ closeDelay = 300000,
84
+ autoRefresh = true,
85
+ refreshInterval = 540000,
86
+ verbose = false,
87
+ watchdogTimeout = 30000,
88
+ } = {}) {
56
89
  if (this._running) return;
57
90
  try {
58
91
  this.verbose = verbose;
59
92
  this.watchdogTimeout = watchdogTimeout;
60
93
 
61
- const [accessToken, buildLabel, sessionId, validCookies] = await getAccessToken(this.cookies, this.proxy, this.verbose);
94
+ const [accessToken, buildLabel, sessionId, language, pushId, validCookies] = await getAccessToken(
95
+ this.cookies, this.proxy, this.verbose,
96
+ );
97
+
62
98
  this.accessToken = accessToken;
63
99
  this.buildLabel = buildLabel;
64
100
  this.sessionId = sessionId;
101
+ this.language = language || 'en';
102
+ this.pushId = pushId || 'feeds/mcudyrk2a4khkz';
65
103
  this.cookies = validCookies;
66
104
  this._running = true;
105
+ this._reqid = Math.floor(Math.random() * 90000) + 10000;
67
106
  this.timeout = timeout;
68
107
  this.autoClose = autoClose;
69
108
  this.closeDelay = closeDelay;
70
109
 
71
110
  if (autoClose) this._resetCloseTask();
111
+
72
112
  this.autoRefresh = autoRefresh;
73
113
  this.refreshInterval = refreshInterval;
114
+
74
115
  if (this.refreshTask) { clearInterval(this.refreshTask); this.refreshTask = null; }
75
116
  if (autoRefresh) this._startAutoRefresh();
117
+
118
+ await this._initRpc();
119
+
76
120
  if (this.verbose) console.log('Gemini client initialized successfully.');
77
121
  } catch (e) { await this.close(); throw e; }
78
122
  }
@@ -103,7 +147,140 @@ class GeminiClient {
103
147
  }, interval);
104
148
  }
105
149
 
106
- async _batchExecute(payloads, retries = 2) {
150
+ async _initRpc() {
151
+ await this._fetchUserStatus();
152
+ await this._sendBardSettings();
153
+ await this._sendBardActivity();
154
+ await this._fetchRecentChats();
155
+ }
156
+
157
+ async _fetchUserStatus() {
158
+ try {
159
+ const response = await this._batchExecute([
160
+ new RPCData({ rpcid: GRPC.GET_USER_STATUS, payload: '[]' }),
161
+ ]);
162
+
163
+ const responseJson = extractJsonFromResponse(response.data);
164
+
165
+ for (const part of responseJson) {
166
+ const partBodyStr = getNestedValue(part, [2]);
167
+ if (!partBodyStr) continue;
168
+ let partBody;
169
+ try { partBody = JSON.parse(partBodyStr); } catch { continue; }
170
+
171
+ const statusCode = getNestedValue(partBody, [14]);
172
+ this.accountStatus = AccountStatus.fromStatusCode(statusCode);
173
+
174
+ if (this.accountStatus !== AccountStatus.AVAILABLE) {
175
+ console.warn(`Account status: ${this.accountStatus.name} - ${this.accountStatus.description}`);
176
+ if ([
177
+ AccountStatus.LOCATION_REJECTED,
178
+ AccountStatus.ACCOUNT_REJECTED,
179
+ AccountStatus.ACCESS_TEMPORARILY_UNAVAILABLE,
180
+ AccountStatus.ACCOUNT_REJECTED_BY_GUARDIAN,
181
+ AccountStatus.GUARDIAN_APPROVAL_REQUIRED,
182
+ ].includes(this.accountStatus)) continue;
183
+ }
184
+
185
+ const modelsList = getNestedValue(partBody, [15]);
186
+ if (!Array.isArray(modelsList)) continue;
187
+
188
+ const tierFlags = (() => {
189
+ const tf = getNestedValue(partBody, [16], []);
190
+ return Array.isArray(tf) ? tf : [];
191
+ })();
192
+ const capabilityFlags = (() => {
193
+ const cf = getNestedValue(partBody, [17], []);
194
+ return Array.isArray(cf) ? cf : [];
195
+ })();
196
+ const [capacity, capacityField] = AvailableModel.computeCapacity(tierFlags, capabilityFlags);
197
+ const idNameMapping = AvailableModel.buildModelIdNameMapping();
198
+
199
+ for (const modelData of modelsList) {
200
+ if (!Array.isArray(modelData)) continue;
201
+ const modelId = getNestedValue(modelData, [0], '');
202
+ const displayName = getNestedValue(modelData, [1], '');
203
+ const description = getNestedValue(modelData, [2], '');
204
+
205
+ if (modelId && displayName) {
206
+ let isModelAvailable = true;
207
+ if (this.accountStatus === AccountStatus.UNAUTHENTICATED) {
208
+ if (modelId !== Model.modelId(Model.BASIC_FLASH)) {
209
+ isModelAvailable = false;
210
+ }
211
+ }
212
+ const model = new AvailableModel({
213
+ model_id: modelId,
214
+ model_name: idNameMapping[modelId] || '',
215
+ display_name: displayName,
216
+ description,
217
+ capacity,
218
+ capacity_field: capacityField,
219
+ is_available: isModelAvailable,
220
+ });
221
+ this._modelRegistry[modelId] = model;
222
+ }
223
+ }
224
+ return;
225
+ }
226
+ } catch (e) {
227
+ if (this.verbose) console.debug(`_fetchUserStatus failed: ${e.message}`);
228
+ }
229
+ }
230
+
231
+ async _sendBardSettings() {
232
+ await this._batchExecute([
233
+ new RPCData({ rpcid: GRPC.BARD_SETTINGS, payload: BARD_SETTINGS_PAYLOAD }),
234
+ ]);
235
+ }
236
+
237
+ async _sendBardActivity() {
238
+ await this._batchExecute([
239
+ new RPCData({ rpcid: GRPC.BARD_SETTINGS, payload: '[[["bard_activity_enabled"]]]' }),
240
+ ]);
241
+ }
242
+
243
+ listModels() {
244
+ const vals = Object.values(this._modelRegistry);
245
+ return vals.length > 0 ? vals : null;
246
+ }
247
+
248
+ _resolveModelByName(name) {
249
+ if (name in this._modelRegistry) return this._modelRegistry[name];
250
+ for (const m of Object.values(this._modelRegistry)) {
251
+ if (m.model_name === name || m.display_name === name) return m;
252
+ }
253
+ return Model.fromName(name);
254
+ }
255
+
256
+ _resolveEnumModel(model) {
257
+ if (model === Model.UNSPECIFIED) return model;
258
+ const headerValue = model.model_header[MODEL_HEADER_KEY];
259
+ if (!headerValue) return model;
260
+ try {
261
+ const parsed = JSON.parse(headerValue);
262
+ const modelId = parsed && parsed[4];
263
+ if (modelId && modelId in this._modelRegistry) return this._modelRegistry[modelId];
264
+ } catch {}
265
+ return model;
266
+ }
267
+
268
+ _resolveModel(model) {
269
+ if (!model || model === Model.UNSPECIFIED) return this._resolveEnumModel(Model.UNSPECIFIED);
270
+ if (model instanceof AvailableModel) return model;
271
+ if (typeof model === 'string') return this._resolveModelByName(model);
272
+ if (typeof model === 'object' && !model.model_name) return Model.fromDict(model);
273
+ if (typeof model === 'object' && model.model_header !== undefined) {
274
+ if (!(model instanceof AvailableModel)) {
275
+ const mid = Model.modelId(model);
276
+ if (mid && mid in this._modelRegistry) return this._modelRegistry[mid];
277
+ }
278
+ return model;
279
+ }
280
+ return model;
281
+ }
282
+
283
+ async _batchExecute(payloads, retries = 2, closeOnError = true) {
107
284
  let lastErr;
108
285
  for (let attempt = 0; attempt <= retries; attempt++) {
109
286
  try {
@@ -112,7 +289,10 @@ class GeminiClient {
112
289
 
113
290
  const params = new URLSearchParams({
114
291
  rpcids: payloads.map(p => p.rpcid).join(','),
115
- _reqid: String(_reqid), rt: 'c', 'source-path': '/app',
292
+ hl: this.language || 'en',
293
+ _reqid: String(_reqid),
294
+ rt: 'c',
295
+ 'source-path': '/app',
116
296
  });
117
297
  if (this.buildLabel) params.set('bl', this.buildLabel);
118
298
  if (this.sessionId) params.set('f.sid', this.sessionId);
@@ -122,179 +302,276 @@ class GeminiClient {
122
302
  'f.req': JSON.stringify([payloads.map(p => p.serialize())]),
123
303
  });
124
304
 
125
- const res = await axios.post(`${Endpoint.BATCH_EXEC}?${params}`, body.toString(), {
126
- headers: { ...Headers.GEMINI, 'Cookie': cookieStr(this.cookies) },
127
- timeout: this.timeout,
128
- ...(this.proxy ? { proxy: parseProxy(this.proxy) } : {}),
129
- validateStatus: null,
130
- });
305
+ const res = await axios.post(
306
+ `${Endpoint.BATCH_EXEC}?${params}`,
307
+ body.toString(),
308
+ {
309
+ headers: {
310
+ ...Headers.GEMINI,
311
+ ...Headers.BATCH_EXEC,
312
+ ...Headers.SAME_DOMAIN,
313
+ 'Cookie': cookieStr(this.cookies),
314
+ },
315
+ timeout: this.timeout,
316
+ ...(this.proxy ? { proxy: parseProxy(this.proxy) } : {}),
317
+ validateStatus: null,
318
+ },
319
+ );
131
320
 
132
321
  Object.assign(this.cookies, parseCookies(res.headers));
133
- if (res.status !== 200) { await this.close(); throw new APIError(`Batch execution failed with status code ${res.status}`); }
322
+ if (res.status !== 200) {
323
+ if (closeOnError) await this.close();
324
+ throw new APIError(`Batch execution failed with status code ${res.status}`);
325
+ }
134
326
  return res;
135
- } catch (e) { lastErr = e; if (attempt < retries) await sleep(1000 * (attempt + 1)); }
327
+ } catch (e) {
328
+ lastErr = e;
329
+ if (attempt < retries) await sleep(1000 * (attempt + 1));
330
+ }
136
331
  }
137
332
  throw lastErr;
138
333
  }
139
334
 
140
- async generateContent({ prompt, files = null, model = Model.UNSPECIFIED, gem = null, chat = null, temporary = false } = {}) {
335
+ async generateContent({
336
+ prompt,
337
+ files = null,
338
+ model = Model.UNSPECIFIED,
339
+ gem = null,
340
+ chat = null,
341
+ temporary = false,
342
+ deep_research = false,
343
+ } = {}) {
141
344
  if (this.autoClose) this._resetCloseTask();
142
- if (!(chat instanceof ChatSession && chat.cid))
143
- this._reqid = Math.floor(Math.random() * 90000) + 10000;
144
345
 
145
346
  let fileData = null;
146
347
  if (files && files.length) {
147
- await this._batchExecute([new RPCData({ rpcid: GRPC.BARD_ACTIVITY, payload: '[[["bard_activity_enabled"]]]' })]);
148
- const uploaded = await Promise.all(files.map(f => uploadFile(f, this.proxy)));
348
+ await this._sendBardActivity();
349
+ const uploaded = await Promise.all(
350
+ files.map(f => uploadFile(f, this.proxy, this.pushId)),
351
+ );
149
352
  fileData = uploaded.map((url, i) => [[url], parseFileName(files[i])]);
150
353
  }
151
354
 
152
355
  try {
153
- await this._batchExecute([new RPCData({ rpcid: GRPC.BARD_ACTIVITY, payload: '[[["bard_activity_enabled"]]]' })]);
154
- const ss = { last_texts: {}, last_thoughts: {}, last_progress_time: Date.now() };
356
+ await this._sendBardActivity();
357
+ const ss = { last_texts: {}, last_thoughts: {}, last_progress_time: Date.now(), is_thinking: false, is_queueing: false };
155
358
  let output = null;
156
- for await (const out of this._generate({ prompt, fileData, model, gem, chat, temporary, ss })) output = out;
359
+ for await (const out of this._generate({
360
+ prompt, fileData, model, gem, chat, temporary, ss, deep_research,
361
+ })) output = out;
157
362
  if (!output) throw new GeminiError('Failed to generate contents. No output data found in response.');
158
363
  if (chat instanceof ChatSession) { output.metadata = chat.metadata; chat.lastOutput = output; }
159
364
  return output;
160
- } finally {}
365
+ } finally {
366
+ }
161
367
  }
162
368
 
163
- async* generateContentStream({ prompt, files = null, model = Model.UNSPECIFIED, gem = null, chat = null, temporary = false } = {}) {
369
+ async* generateContentStream({
370
+ prompt,
371
+ files = null,
372
+ model = Model.UNSPECIFIED,
373
+ gem = null,
374
+ chat = null,
375
+ temporary = false,
376
+ deep_research = false,
377
+ } = {}) {
164
378
  if (this.autoClose) this._resetCloseTask();
165
- if (!(chat instanceof ChatSession && chat.cid))
166
- this._reqid = Math.floor(Math.random() * 90000) + 10000;
167
379
 
168
380
  let fileData = null;
169
381
  if (files && files.length) {
170
- await this._batchExecute([new RPCData({ rpcid: GRPC.BARD_ACTIVITY, payload: '[[["bard_activity_enabled"]]]' })]);
171
- const uploaded = await Promise.all(files.map(f => uploadFile(f, this.proxy)));
382
+ await this._sendBardActivity();
383
+ const uploaded = await Promise.all(
384
+ files.map(f => uploadFile(f, this.proxy, this.pushId)),
385
+ );
172
386
  fileData = uploaded.map((url, i) => [[url], parseFileName(files[i])]);
173
387
  }
174
388
 
175
- await this._batchExecute([new RPCData({ rpcid: GRPC.BARD_ACTIVITY, payload: '[[["bard_activity_enabled"]]]' })]);
176
- const ss = { last_texts: {}, last_thoughts: {}, last_progress_time: Date.now() };
389
+ await this._sendBardActivity();
390
+ const ss = { last_texts: {}, last_thoughts: {} };
177
391
  let output = null;
178
- for await (const out of this._generate({ prompt, fileData, model, gem, chat, temporary, ss })) {
179
- output = out; yield out;
392
+ for await (const out of this._generate({
393
+ prompt, fileData, model, gem, chat, temporary, ss, deep_research,
394
+ })) {
395
+ output = out;
396
+ yield out;
180
397
  }
181
398
  if (output && chat instanceof ChatSession) { output.metadata = chat.metadata; chat.lastOutput = output; }
182
399
  }
183
400
 
184
- async* _generate({ prompt, fileData = null, model = Model.UNSPECIFIED, gem = null, chat = null, temporary = false, ss = null }, retries = 5) {
401
+ async* _generate({
402
+ prompt,
403
+ fileData = null,
404
+ model = Model.UNSPECIFIED,
405
+ gem = null,
406
+ chat = null,
407
+ temporary = false,
408
+ ss = null,
409
+ deep_research = false,
410
+ }, retries = 5) {
185
411
  if (!prompt) throw new Error('Prompt cannot be empty.');
186
- if (typeof model === 'string') model = Model.fromName(model);
187
- else if (model && typeof model === 'object' && !model.model_name) model = Model.fromDict(model);
412
+
413
+ model = this._resolveModel(model);
188
414
 
189
415
  for (let attempt = 0; attempt <= retries; attempt++) {
190
416
  try {
191
- for await (const out of this._stream({ prompt, fileData, model, gem, chat, temporary, ss })) yield out;
417
+ for await (const out of this._stream({ prompt, fileData, model, gem, chat, temporary, ss, deep_research })) yield out;
192
418
  return;
193
419
  } catch (e) {
194
- if (e instanceof GeminiError || e instanceof ModelInvalid || e instanceof UsageLimitExceeded || e instanceof TemporarilyBlocked) throw e;
420
+ if (
421
+ e instanceof GeminiError || e instanceof ModelInvalid ||
422
+ e instanceof UsageLimitExceeded || e instanceof TemporarilyBlocked
423
+ ) throw e;
195
424
  if (attempt >= retries) throw e;
196
425
  await sleep(1000 * (attempt + 1));
197
426
  }
198
427
  }
199
428
  }
200
429
 
201
- async* _stream({ prompt, fileData = null, model = Model.UNSPECIFIED, gem = null, chat = null, temporary = false, ss = null }) {
430
+ async* _stream({ prompt, fileData = null, model = Model.UNSPECIFIED, gem = null, chat = null, temporary = false, ss = null, deep_research = false }) {
202
431
  const _reqid = this._reqid;
203
432
  this._reqid += 100000;
204
- const gemId = gem && typeof gem === 'object' ? gem.id : gem;
433
+ const gemId = gem instanceof Gem ? gem.id : gem;
434
+
435
+ const chatBackup = chat instanceof ChatSession ? {
436
+ metadata: [...chat.metadata],
437
+ cid: chat.cid,
438
+ rid: chat.rid,
439
+ rcid: chat.rcid,
440
+ } : null;
205
441
 
206
442
  const inner = new Array(69).fill(null);
207
443
  inner[0] = [prompt, 0, null, fileData, null, null, 0];
208
- inner[2] = chat ? chat.metadata : ['', '', '', null, null, null, null, null, null, ''];
209
- inner[7] = 1;
210
- if (gemId) inner[19] = gemId;
444
+ inner[1] = [this.language || 'en'];
445
+ inner[2] = chat instanceof ChatSession ? chat.metadata : [...DEFAULT_METADATA];
446
+ if (deep_research) {
447
+ inner[3] = '!' + crypto.randomBytes(1950).toString('base64url');
448
+ inner[4] = crypto.randomUUID().replace(/-/g, '');
449
+ }
450
+ inner[6] = [1];
451
+ inner[STREAMING_FLAG_INDEX] = 1;
452
+ inner[10] = 1;
453
+ inner[11] = 0;
454
+ inner[17] = [[0]];
455
+ inner[18] = 0;
456
+ if (gemId) inner[GEM_FLAG_INDEX] = gemId;
457
+ inner[27] = 1;
458
+ inner[30] = [4];
459
+ inner[41] = [1];
211
460
  if (temporary) inner[TEMPORARY_CHAT_FLAG_INDEX] = 1;
212
- inner[1] = ['en']; inner[6] = [0]; inner[10] = 1; inner[11] = 0;
213
- inner[17] = [[0]]; inner[18] = 0; inner[27] = 1; inner[30] = [4];
214
- inner[41] = [1]; inner[53] = 0; inner[61] = []; inner[68] = 1;
215
- const uid = uuidv4();
461
+ if (deep_research) inner[49] = 1;
462
+ inner[53] = 0;
463
+ if (deep_research) {
464
+ inner[54] = [[[[[1]]]]];
465
+ inner[55] = [[1]];
466
+ }
467
+ inner[61] = [];
468
+ inner[68] = 2;
469
+
470
+ const uid = uuidv4().toUpperCase();
216
471
  inner[59] = uid;
217
472
 
218
- const params = new URLSearchParams({ _reqid: String(_reqid), rt: 'c' });
473
+ const params = new URLSearchParams({
474
+ hl: this.language || 'en',
475
+ _reqid: String(_reqid),
476
+ rt: 'c',
477
+ });
219
478
  if (this.buildLabel) params.set('bl', this.buildLabel);
220
479
  if (this.sessionId) params.set('f.sid', this.sessionId);
221
480
 
222
- const body = new URLSearchParams({ at: this.accessToken || '', 'f.req': JSON.stringify([null, JSON.stringify(inner)]) });
223
-
224
- if (ss) {
225
- if (!('original_cid' in ss)) ss.original_cid = chat ? chat.cid : null;
226
- if (!('original_rcid' in ss)) ss.original_rcid = chat instanceof ChatSession ? chat.rcid : null;
227
- if (!('had_response_data' in ss)) ss.had_response_data = false;
228
-
229
- if (chat && chat.cid && (!ss.original_cid || ss.had_response_data)) {
230
- const delays = [30000, 45000, 60000, 90000];
231
- let allStale = true;
232
- for (let i = 0; i < delays.length; i++) {
233
- console.warn(`Stream failed for cid=${chat.cid}. READ_CHAT attempt ${i + 1}/${delays.length}: waiting ${delays[i] / 1000}s...`);
234
- await sleep(delays[i]);
235
- try {
236
- const recovered = await this.fetchLatestChatResponse(chat.cid);
237
- if (recovered) {
238
- if (ss.original_cid && recovered.rcid && recovered.rcid === ss.original_rcid) continue;
239
- if (chat instanceof ChatSession) chat.metadata = recovered.metadata;
240
- yield recovered; return;
241
- }
242
- allStale = false;
243
- } catch (e) { allStale = false; console.warn(`READ_CHAT attempt ${i + 1} failed: ${e.message}`); }
244
- }
245
- if (allStale) {
246
- if (chat instanceof ChatSession) { chat.rid = ''; chat.rcid = ''; }
247
- ss.had_response_data = false;
248
- throw new APIError(`Stream failed for cid=${chat.cid}. All READ_CHAT stale. Retrying.`);
249
- }
250
- throw new GeminiError(`Stream failed for cid=${chat.cid}. Recovery returned no data.`);
251
- }
252
- }
253
-
254
- const res = await axios.post(`${Endpoint.GENERATE}?${params}`, body.toString(), {
255
- headers: { ...Headers.GEMINI, ...model.model_header, 'x-goog-ext-525005358-jspb': `["${uid}",1]`, 'Cookie': cookieStr(this.cookies) },
256
- responseType: 'stream', timeout: this.timeout, validateStatus: null,
257
- ...(this.proxy ? { proxy: parseProxy(this.proxy) } : {}),
481
+ const body = new URLSearchParams({
482
+ at: this.accessToken || '',
483
+ 'f.req': JSON.stringify([null, JSON.stringify(inner)]),
258
484
  });
259
485
 
260
- if (res.status !== 200) { await this.close(); throw new APIError(`Failed to generate contents. Status: ${res.status}`); }
486
+ let hasGeneratedText = false;
487
+ let sleepTime = 10000;
488
+
489
+ const res = await axios.post(
490
+ `${Endpoint.GENERATE}?${params}`,
491
+ body.toString(),
492
+ {
493
+ headers: {
494
+ ...Headers.GEMINI,
495
+ ...model.model_header,
496
+ 'x-goog-ext-525005358-jspb': `["${uid}",1]`,
497
+ ...Headers.SAME_DOMAIN,
498
+ 'Cookie': cookieStr(this.cookies),
499
+ },
500
+ responseType: 'stream',
501
+ timeout: this.timeout,
502
+ validateStatus: null,
503
+ ...(this.proxy ? { proxy: parseProxy(this.proxy) } : {}),
504
+ },
505
+ );
506
+
507
+ if (res.status !== 200) {
508
+ await this.close();
509
+ throw new APIError(`Failed to generate contents. Status: ${res.status}`);
510
+ }
261
511
  Object.assign(this.cookies, parseCookies(res.headers));
262
512
 
263
- const lTxt = ss ? ss.last_texts : {}, lThought = ss ? ss.last_thoughts : {};
513
+ const lTxt = ss ? ss.last_texts : {};
514
+ const lThought = ss ? ss.last_thoughts : {};
264
515
  let lastProg = Date.now();
265
- let isThinking = false, isQueueing = false, hasCandidates = false, isCompleted = false, isFinalChunk = false;
516
+ let isThinking = false, isQueueing = false, hasCandidates = false;
517
+ let isCompleted = false, isFinalChunk = false;
518
+ let cid = chat instanceof ChatSession ? chat.cid : '';
519
+ let rid = chat instanceof ChatSession ? chat.rid : '';
266
520
  let buf = '';
267
521
 
268
- const processParts = parts => {
522
+ const processParts = (parts) => {
269
523
  const outs = [];
270
524
  for (const part of parts) {
271
525
  const ec = getNestedValue(part, [5, 2, 0, 1, 0]);
272
526
  if (ec) {
273
527
  switch (ec) {
274
- case ErrorCode.USAGE_LIMIT_EXCEEDED: throw new UsageLimitExceeded(`Usage limit exceeded for model '${model.model_name}'.`);
275
- case ErrorCode.MODEL_INCONSISTENT: throw new ModelInvalid('Model inconsistent with conversation history.');
276
- case ErrorCode.MODEL_HEADER_INVALID: throw new ModelInvalid(`Model '${model.model_name}' unavailable or request structure outdated.`);
277
- case ErrorCode.IP_TEMPORARILY_BLOCKED: throw new TemporarilyBlocked('IP temporarily blocked by Google.');
278
- case ErrorCode.TEMPORARY_ERROR_1013: throw new APIError('Temporary error (1013). Retrying...');
279
- default: throw new APIError(`Unknown API error code: ${ec}.`);
528
+ case ErrorCode.USAGE_LIMIT_EXCEEDED:
529
+ throw new UsageLimitExceeded(`Usage limit exceeded for model '${model.model_name}'.`);
530
+ case ErrorCode.MODEL_INCONSISTENT:
531
+ throw new ModelInvalid('Model inconsistent with conversation history.');
532
+ case ErrorCode.MODEL_HEADER_INVALID:
533
+ throw new ModelInvalid(`Model '${model.model_name}' unavailable or request structure outdated.`);
534
+ case ErrorCode.IP_TEMPORARILY_BLOCKED:
535
+ throw new TemporarilyBlocked('IP temporarily blocked by Google.');
536
+ case ErrorCode.TEMPORARY_ERROR_1013:
537
+ throw new APIError('Temporary error (1013). Retrying...');
538
+ default:
539
+ throw new APIError(`Unknown API error code: ${ec}.`);
280
540
  }
281
541
  }
282
542
 
283
- if (JSON.stringify(part).includes('data_analysis_tool')) isThinking = true;
543
+ const toolName = JSON.stringify(part).includes('data_analysis_tool');
544
+ if (toolName) { isThinking = true; isQueueing = false; }
545
+
284
546
  const status = getNestedValue(part, [5]);
285
- if (Array.isArray(status) && status.length) isQueueing = true;
547
+ if (Array.isArray(status) && status.length) {
548
+ if (!isThinking) isQueueing = true;
549
+ }
286
550
 
287
551
  const innerStr = getNestedValue(part, [2]);
288
552
  if (!innerStr) continue;
289
- let pj; try { pj = JSON.parse(innerStr); } catch { continue; }
553
+ let pj;
554
+ try { pj = JSON.parse(innerStr); } catch { continue; }
290
555
 
291
556
  const mData = getNestedValue(pj, [1]);
292
- if (mData && chat instanceof ChatSession) { chat.metadata = mData; if (ss) ss.had_response_data = true; }
557
+ if (mData) {
558
+ const newCid = getNestedValue(mData, [0]);
559
+ const newRid = getNestedValue(mData, [1]);
560
+ if (newCid) cid = newCid;
561
+ if (newRid) rid = newRid;
562
+ if (chat instanceof ChatSession) chat.metadata = mData;
563
+ }
293
564
 
294
565
  const ctx = getNestedValue(pj, [25]);
295
566
  if (typeof ctx === 'string') {
296
- isCompleted = true; isThinking = false; isQueueing = false;
297
- if (chat instanceof ChatSession) { const m = [...chat.metadata]; m[9] = ctx; chat.metadata = m; }
567
+ isFinalChunk = true;
568
+ isThinking = false;
569
+ isQueueing = false;
570
+ if (chat instanceof ChatSession) {
571
+ const m = [...chat.metadata];
572
+ m[9] = ctx;
573
+ chat.metadata = m;
574
+ }
298
575
  }
299
576
 
300
577
  const clist = getNestedValue(pj, [4], []);
@@ -303,49 +580,76 @@ class GeminiClient {
303
580
  const outCands = [];
304
581
  for (let i = 0; i < clist.length; i++) {
305
582
  const cd = clist[i];
306
- const rcid = getNestedValue(cd, [0]); if (!rcid) continue;
583
+ const rcid = getNestedValue(cd, [0]);
584
+ if (!rcid) continue;
307
585
  if (chat instanceof ChatSession) chat.rcid = rcid;
308
586
 
309
- let txt = getNestedValue(cd, [1, 0], '');
310
- if (/^http:\/\/googleusercontent\.com\/card_content\/\d+/.test(txt))
311
- txt = getNestedValue(cd, [22, 0]) || txt;
312
- txt = txt.replace(/http:\/\/googleusercontent\.com\/\w+\/\d+\n*/g, '');
587
+ const [text, thoughts, webImgs, genImgs, genVideos, genMedia] = this._parseCandidate(cd, cid, rid, rcid);
313
588
 
314
- const thoughts = getNestedValue(cd, [37, 0, 0]) || '';
315
- const webImgs = [], genImgs = [];
316
-
317
- for (const wi of (getNestedValue(cd, [12, 1], []) || [])) {
318
- const url = getNestedValue(wi, [0, 0, 0]);
319
- if (url) webImgs.push(new WebImage({ url, title: getNestedValue(wi, [7, 0], ''), alt: getNestedValue(wi, [0, 4], ''), proxy: this.proxy }));
320
- }
321
- for (const gi of (getNestedValue(cd, [12, 7, 0], []) || [])) {
322
- const url = getNestedValue(gi, [0, 3, 3]);
323
- if (url) {
324
- const imgNum = getNestedValue(gi, [3, 6]);
325
- genImgs.push(new GeneratedImage({ url, title: imgNum ? `[Generated Image ${imgNum}]` : '[Generated Image]', alt: getNestedValue(gi, [3, 5, 0], ''), proxy: this.proxy, cookies: this.cookies }));
589
+ let drPlan = null;
590
+ if (deep_research) {
591
+ const planData = extractDeepResearchPlan(cd, text);
592
+ if (planData) {
593
+ drPlan = new DeepResearchPlan({
594
+ ...planData,
595
+ cid: chat instanceof ChatSession ? chat.cid : null,
596
+ });
326
597
  }
327
598
  }
328
599
 
329
- isFinalChunk = Array.isArray(getNestedValue(cd, [2])) || getNestedValue(cd, [8, 0], 1) === 2;
600
+ const indicator = getNestedValue(cd, [8, 0]);
601
+ isCompleted = indicator === 2;
602
+
603
+ const lastSentText = lTxt[rcid] || lTxt[`idx_${i}`] || '';
604
+ const [td, nft] = getDeltaByFpLen(text, lastSentText, isCompleted || indicator == null);
330
605
 
331
- const [td, nft] = getDeltaByFpLen(txt, lTxt[rcid] || lTxt[`idx_${i}`] || '', isFinalChunk);
332
606
  let thdelta = '', nfth = '';
333
- if (thoughts) [thdelta, nfth] = getDeltaByFpLen(thoughts, lThought[rcid] || lThought[`idx_${i}`] || '', isFinalChunk);
607
+ if (thoughts) {
608
+ const lastSentThought = lThought[rcid] || lThought[`idx_${i}`] || '';
609
+ [thdelta, nfth] = getDeltaByFpLen(thoughts, lastSentThought, isCompleted || indicator == null);
610
+ }
611
+
612
+ if (td || thdelta || webImgs.length || genImgs.length || genVideos.length || genMedia.length || drPlan) {
613
+ hasCandidates = true;
614
+ }
334
615
 
335
- if (td || thdelta || webImgs.length || genImgs.length) hasCandidates = true;
336
616
  lTxt[rcid] = lTxt[`idx_${i}`] = nft;
337
617
  lThought[rcid] = lThought[`idx_${i}`] = nfth;
338
618
 
339
- outCands.push(new Candidate({ rcid, text: txt, text_delta: td, thoughts: thoughts || null, thoughts_delta: thdelta, web_images: webImgs, generated_images: genImgs }));
619
+ outCands.push(new Candidate({
620
+ rcid,
621
+ text,
622
+ text_delta: td,
623
+ thoughts: thoughts || null,
624
+ thoughts_delta: thdelta,
625
+ web_images: webImgs,
626
+ generated_images: genImgs,
627
+ generated_videos: genVideos,
628
+ generated_media: genMedia,
629
+ deep_research_plan: drPlan,
630
+ }));
340
631
  }
341
632
 
342
- if (outCands.length) { isThinking = false; isQueueing = false; outs.push(new ModelOutput(getNestedValue(pj, [1], []), outCands)); }
633
+ if (outCands.length) {
634
+ isThinking = false;
635
+ isQueueing = false;
636
+ outs.push(new ModelOutput(getNestedValue(pj, [1], []), outCands));
637
+ }
343
638
  }
344
639
  return outs;
345
640
  };
346
641
 
347
642
  const yielded = [];
643
+ let streamErr = null;
644
+
348
645
  await new Promise((resolve, reject) => {
646
+ const watchdog = setInterval(() => {
647
+ if (!isThinking && !isQueueing && (Date.now() - lastProg) > Math.min(this.timeout, this.watchdogTimeout)) {
648
+ clearInterval(watchdog);
649
+ reject(new APIError('Response stalled (zombie stream).'));
650
+ }
651
+ }, 1000);
652
+
349
653
  res.data.on('data', chunk => {
350
654
  try {
351
655
  buf += chunk.toString('utf8');
@@ -354,57 +658,269 @@ class GeminiClient {
354
658
  buf = rem;
355
659
  const outs = processParts(parts);
356
660
  for (const o of outs) yielded.push(o);
357
- if (outs.length || isThinking || isQueueing) { lastProg = Date.now(); if (ss) ss.last_progress_time = lastProg; }
358
- else if (Date.now() - lastProg > Math.min(this.timeout, this.watchdogTimeout))
359
- reject(new APIError('Response stalled (zombie stream).'));
360
- } catch (e) { reject(e); }
661
+ if (outs.length || isThinking || isQueueing) lastProg = Date.now();
662
+ } catch (e) {
663
+ clearInterval(watchdog);
664
+ reject(e);
665
+ }
361
666
  });
667
+
362
668
  res.data.on('end', () => {
669
+ clearInterval(watchdog);
363
670
  try {
364
- if (buf) { const [p] = parseResponseByFrame(buf); for (const o of processParts(p)) yielded.push(o); }
365
- if (!(isCompleted || isFinalChunk) || isThinking || isQueueing) reject(new APIError('Stream interrupted or truncated.'));
671
+ if (buf) {
672
+ const [p] = parseResponseByFrame(buf);
673
+ for (const o of processParts(p)) yielded.push(o);
674
+ }
675
+ if (!isCompleted && !isFinalChunk) reject(new APIError('Stream interrupted or truncated.'));
366
676
  else resolve();
367
677
  } catch (e) { reject(e); }
368
678
  });
369
- res.data.on('error', e => reject(new APIError(`Stream error: ${e.message}`)));
679
+
680
+ res.data.on('error', e => { clearInterval(watchdog); reject(new APIError(`Stream error: ${e.message}`)); });
370
681
  });
371
682
 
372
- for (const o of yielded) yield o;
683
+ hasGeneratedText = yielded.length > 0;
684
+
685
+ if (!isCompleted && !isFinalChunk && cid) {
686
+ let pollStart = Date.now();
687
+ while (true) {
688
+ if ((Date.now() - pollStart) > this.timeout) {
689
+ await this.close();
690
+ if (hasGeneratedText) throw new GeminiError('Connection to Gemini lost. Recovery timed out.');
691
+ else throw new APIError('read_chat polling timed out waiting for the model to finish.');
692
+ }
693
+ await this._sendBardActivity();
694
+ const recovered = await this.readChat(cid);
695
+ if (
696
+ recovered && recovered.turns && recovered.turns.length > 0 &&
697
+ recovered.turns[0].role === 'model'
698
+ ) {
699
+ const recoveredOut = recovered.turns[0].model_output;
700
+ if (recoveredOut && recoveredOut.candidates && (recoveredOut.text || recoveredOut.thoughts)) {
701
+ const recRcid = recoveredOut.rcid;
702
+ const prevRcid = chatBackup ? chatBackup.rcid : '';
703
+ if (recRcid !== prevRcid) {
704
+ if (chat instanceof ChatSession) {
705
+ recoveredOut.metadata = chat.metadata;
706
+ chat.rcid = recRcid;
707
+ }
708
+ yield recoveredOut;
709
+ return;
710
+ }
711
+ }
712
+ }
713
+ await sleep(sleepTime);
714
+ }
715
+ }
716
+
717
+ for (const o of yielded) {
718
+ hasGeneratedText = true;
719
+ yield o;
720
+ }
721
+ }
722
+
723
+ _parseCandidate(candidateData, cid, rid, rcid) {
724
+ let text = getNestedValue(candidateData, [1, 0], '');
725
+ if (CARD_CONTENT_RE.test(text)) {
726
+ text = getNestedValue(candidateData, [22, 0]) || text;
727
+ }
728
+ ARTIFACTS_RE.lastIndex = 0;
729
+ text = text.replace(ARTIFACTS_RE, '');
730
+
731
+ const thoughts = getNestedValue(candidateData, [37, 0, 0]) || '';
732
+
733
+ const webImages = [];
734
+ for (const [imgIdx, wi] of (getNestedValue(candidateData, [12, 1], []) || []).entries()) {
735
+ const url = getNestedValue(wi, [0, 0, 0]);
736
+ if (url) {
737
+ webImages.push(new WebImage({
738
+ url,
739
+ title: `[Image ${imgIdx + 1}]`,
740
+ alt: getNestedValue(wi, [0, 4], ''),
741
+ proxy: this.proxy,
742
+ }));
743
+ }
744
+ }
745
+
746
+ const generatedImages = [];
747
+ const genImgSources = [
748
+ ...(getNestedValue(candidateData, [12, 7, 0], []) || []),
749
+ ...(getNestedValue(candidateData, [12, 0, '8', 0], []) || []),
750
+ ];
751
+ for (const [imgIdx, gi] of genImgSources.entries()) {
752
+ const url = getNestedValue(gi, [0, 3, 3]);
753
+ if (url) {
754
+ let imageId = getNestedValue(gi, [1, 0]);
755
+ if (!imageId) imageId = `http://googleusercontent.com/image_generation_content/${imgIdx}`;
756
+ generatedImages.push(new GeneratedImage({
757
+ url,
758
+ title: `[Generated Image ${imgIdx}]`,
759
+ alt: getNestedValue(gi, [0, 3, 2], ''),
760
+ proxy: this.proxy,
761
+ client_ref: this,
762
+ cid,
763
+ rid,
764
+ rcid,
765
+ image_id: imageId,
766
+ }));
767
+ }
768
+ }
769
+
770
+ const generatedVideos = [];
771
+ const videoInfo = getNestedValue(candidateData, [12, 59, 0, 0, 0], []);
772
+ if (videoInfo) {
773
+ const urls = getNestedValue(videoInfo, [0, 7], []);
774
+ if (Array.isArray(urls) && urls.length >= 2) {
775
+ generatedVideos.push(new GeneratedVideo({
776
+ url: urls[1],
777
+ thumbnail: urls[0],
778
+ cid,
779
+ rid,
780
+ rcid,
781
+ client_ref: this,
782
+ proxy: this.proxy,
783
+ }));
784
+ }
785
+ }
786
+
787
+ const generatedMedia = [];
788
+ const mediaData = getNestedValue(candidateData, [12, 86], []);
789
+ if (mediaData) {
790
+ let mp3Url = '', mp3Thumb = '';
791
+ const mp3List = getNestedValue(mediaData, [0, 1, 7], []);
792
+ if (Array.isArray(mp3List) && mp3List.length >= 2) {
793
+ mp3Thumb = mp3List[0];
794
+ mp3Url = mp3List[1];
795
+ }
796
+ let mp4Url = '', mp4Thumb = '';
797
+ const mp4List = getNestedValue(mediaData, [1, 1, 7], []);
798
+ if (Array.isArray(mp4List) && mp4List.length >= 2) {
799
+ mp4Thumb = mp4List[0];
800
+ mp4Url = mp4List[1];
801
+ }
802
+ if (mp3Url || mp4Url) {
803
+ generatedMedia.push(new GeneratedMedia({
804
+ url: mp4Url,
805
+ thumbnail: mp4Thumb,
806
+ mp3_url: mp3Url,
807
+ mp3_thumbnail: mp3Thumb,
808
+ cid,
809
+ rid,
810
+ rcid,
811
+ client_ref: this,
812
+ proxy: this.proxy,
813
+ }));
814
+ }
815
+ }
816
+
817
+ return [text, thoughts, webImages, generatedImages, generatedVideos, generatedMedia];
818
+ }
819
+
820
+ async _getFullSizeImage(cid, rid, rcid, imageId) {
821
+ try {
822
+ const payload = [
823
+ [
824
+ [null, null, null, [null, null, null, null, null, '']],
825
+ [imageId, 0],
826
+ null,
827
+ [19, ''],
828
+ null, null, null, null, null, '',
829
+ ],
830
+ [rid, rcid, cid, null, ''],
831
+ 1, 0, 1,
832
+ ];
833
+
834
+ const response = await this._batchExecute([
835
+ new RPCData({
836
+ rpcid: GRPC.GET_FULL_SIZE_IMAGE,
837
+ payload: JSON.stringify(payload),
838
+ }),
839
+ ]);
840
+
841
+ const responseData = extractJsonFromResponse(response.data);
842
+ const bodyStr = getNestedValue(responseData, [0, 2], '[]');
843
+ return getNestedValue(JSON.parse(bodyStr), [0]);
844
+ } catch {
845
+ return null;
846
+ }
373
847
  }
374
848
 
375
- startChat(opts = {}) { return new ChatSession({ geminiclient: this, ...opts }); }
849
+ startChat(opts = {}) {
850
+ return new ChatSession({ geminiclient: this, ...opts });
851
+ }
376
852
  }
377
853
 
378
- applyMixins(GeminiClient, ChatMixin, GemMixin);
854
+ applyMixins(GeminiClient, ChatMixin, GemMixin, ResearchMixin);
379
855
 
380
856
  class ChatSession {
381
- constructor({ geminiclient, metadata = null, cid = null, rid = null, rcid = null, model = null, gem = null } = {}) {
382
- this._metadata = ['', '', '', null, null, null, null, null, null, ''];
857
+ constructor({
858
+ geminiclient,
859
+ metadata = null,
860
+ cid = '',
861
+ rid = '',
862
+ rcid = '',
863
+ model = Model.UNSPECIFIED,
864
+ gem = null,
865
+ } = {}) {
866
+ this._metadata = [...DEFAULT_METADATA];
383
867
  this.geminiclient = geminiclient;
384
868
  this.lastOutput = null;
385
- this.model = ChatSession._resolveModel(model);
869
+ this.model = this._resolveModel(model);
386
870
  this.gem = gem;
871
+
387
872
  if (metadata) this.metadata = metadata;
388
873
  if (cid) this.cid = cid;
389
874
  if (rid) this.rid = rid;
390
875
  if (rcid) this.rcid = rcid;
391
876
  }
392
877
 
878
+ _resolveModel(model) {
879
+ if (!model || model === Model.UNSPECIFIED) return Model.UNSPECIFIED;
880
+ if (typeof model === 'string') return Model.fromName(model);
881
+ if (typeof model === 'object' && model.model_header === undefined) return Model.fromDict(model);
882
+ return model;
883
+ }
884
+
393
885
  get metadata() { return this._metadata; }
394
886
  set metadata(v) {
395
887
  if (!Array.isArray(v)) return;
396
- for (let i = 0; i < v.length && i < 10; i++) if (v[i] != null) this._metadata[i] = v[i];
888
+ for (let i = 0; i < v.length && i < 10; i++) {
889
+ if (v[i] != null) this._metadata[i] = v[i];
890
+ }
397
891
  }
398
- get cid() { return this._metadata[0]; } set cid(v) { this._metadata[0] = v; }
399
- get rid() { return this._metadata[1]; } set rid(v) { this._metadata[1] = v; }
400
- get rcid() { return this._metadata[2]; } set rcid(v) { this._metadata[2] = v; }
401
892
 
402
- async sendMessage({ prompt, files = null, temporary = false } = {}) {
403
- return this.geminiclient.generateContent({ prompt, files, model: this.model, gem: this.gem, chat: this, temporary });
893
+ get cid() { return this._metadata[0]; }
894
+ set cid(v) { this._metadata[0] = v; }
895
+
896
+ get rid() { return this._metadata[1]; }
897
+ set rid(v) { this._metadata[1] = v; }
898
+
899
+ get rcid() { return this._metadata[2]; }
900
+ set rcid(v) { this._metadata[2] = v; }
901
+
902
+ async sendMessage({ prompt, files = null, temporary = false, deep_research = false } = {}) {
903
+ return this.geminiclient.generateContent({
904
+ prompt,
905
+ files,
906
+ model: this.model,
907
+ gem: this.gem,
908
+ chat: this,
909
+ temporary,
910
+ deep_research,
911
+ });
404
912
  }
405
913
 
406
- async* sendMessageStream({ prompt, files = null, temporary = false } = {}) {
407
- for await (const out of this.geminiclient.generateContentStream({ prompt, files, model: this.model, gem: this.gem, chat: this, temporary })) yield out;
914
+ async* sendMessageStream({ prompt, files = null, temporary = false, deep_research = false } = {}) {
915
+ for await (const out of this.geminiclient.generateContentStream({
916
+ prompt,
917
+ files,
918
+ model: this.model,
919
+ gem: this.gem,
920
+ chat: this,
921
+ temporary,
922
+ deep_research,
923
+ })) yield out;
408
924
  }
409
925
 
410
926
  chooseCandidate(index) {
@@ -415,14 +931,14 @@ class ChatSession {
415
931
  return this.lastOutput;
416
932
  }
417
933
 
418
- static _resolveModel(model) {
419
- if (!model) return Model.UNSPECIFIED;
420
- if (typeof model === 'string') return Model.fromName(model);
421
- if (typeof model === 'object' && !model.model_name) return Model.fromDict(model);
422
- return model;
934
+ async readHistory(limit = 10) {
935
+ if (!this.cid) return null;
936
+ return this.geminiclient.readChat(this.cid, limit);
423
937
  }
424
938
 
425
- toString() { return `ChatSession(cid='${this.cid}', rid='${this.rid}', rcid='${this.rcid}')`; }
939
+ toString() {
940
+ return `ChatSession(cid='${this.cid}', rid='${this.rid}', rcid='${this.rcid}')`;
941
+ }
426
942
  }
427
943
 
428
- module.exports = { GeminiClient, ChatSession };
944
+ module.exports = { GeminiClient, ChatSession };