gemini-reverse 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -154
- package/client.js +668 -152
- package/components/chatMixin.js +129 -70
- package/components/index.js +2 -1
- package/components/researchMixin.js +202 -0
- package/constants.js +157 -36
- package/index.d.ts +254 -46
- package/index.js +35 -5
- package/package.json +1 -1
- package/types/availablemodel.js +76 -0
- package/types/candidate.js +31 -17
- package/types/chathistory.js +36 -0
- package/types/chatinfo.js +23 -0
- package/types/index.js +26 -2
- package/types/modeloutput.js +4 -1
- package/types/research.js +65 -0
- package/types/researchresult.js +21 -0
- package/types/video.js +195 -0
- package/utils/accessToken.js +15 -7
- package/utils/index.js +22 -3
- package/utils/parsing.js +67 -38
- package/utils/research.js +175 -0
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 {
|
|
6
|
-
|
|
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(
|
|
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({
|
|
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(
|
|
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
|
|
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
|
-
|
|
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(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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) {
|
|
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) {
|
|
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({
|
|
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.
|
|
148
|
-
const uploaded = await Promise.all(
|
|
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.
|
|
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({
|
|
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({
|
|
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.
|
|
171
|
-
const uploaded = await Promise.all(
|
|
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.
|
|
176
|
-
const ss = { last_texts: {}, last_thoughts: {}
|
|
389
|
+
await this._sendBardActivity();
|
|
390
|
+
const ss = { last_texts: {}, last_thoughts: {} };
|
|
177
391
|
let output = null;
|
|
178
|
-
for await (const out of this._generate({
|
|
179
|
-
|
|
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({
|
|
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
|
-
|
|
187
|
-
|
|
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 (
|
|
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
|
|
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[
|
|
209
|
-
inner[
|
|
210
|
-
if (
|
|
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
|
-
|
|
213
|
-
inner[
|
|
214
|
-
|
|
215
|
-
|
|
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({
|
|
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({
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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 : {}
|
|
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
|
|
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:
|
|
275
|
-
|
|
276
|
-
case ErrorCode.
|
|
277
|
-
|
|
278
|
-
case ErrorCode.
|
|
279
|
-
|
|
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
|
-
|
|
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)
|
|
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;
|
|
553
|
+
let pj;
|
|
554
|
+
try { pj = JSON.parse(innerStr); } catch { continue; }
|
|
290
555
|
|
|
291
556
|
const mData = getNestedValue(pj, [1]);
|
|
292
|
-
if (mData
|
|
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
|
-
|
|
297
|
-
|
|
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]);
|
|
583
|
+
const rcid = getNestedValue(cd, [0]);
|
|
584
|
+
if (!rcid) continue;
|
|
307
585
|
if (chat instanceof ChatSession) chat.rcid = rcid;
|
|
308
586
|
|
|
309
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
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)
|
|
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({
|
|
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) {
|
|
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)
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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) {
|
|
365
|
-
|
|
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
|
-
|
|
679
|
+
|
|
680
|
+
res.data.on('error', e => { clearInterval(watchdog); reject(new APIError(`Stream error: ${e.message}`)); });
|
|
370
681
|
});
|
|
371
682
|
|
|
372
|
-
|
|
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 = {}) {
|
|
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({
|
|
382
|
-
|
|
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 =
|
|
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++)
|
|
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
|
-
|
|
403
|
-
|
|
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({
|
|
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
|
-
|
|
419
|
-
if (!
|
|
420
|
-
|
|
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() {
|
|
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 };
|