gemini-reverse 1.0.5 → 1.0.7
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 +96 -15
- package/client.js +287 -41
- package/components/chatMixin.js +1 -0
- package/components/gemMixin.js +2 -1
- package/constants.js +59 -23
- package/index.d.ts +9 -2
- package/index.js +2 -0
- package/package.json +1 -1
- package/types/availablemodel.js +32 -5
- package/types/candidate.js +16 -2
- package/types/video.js +4 -0
- package/utils/index.js +2 -1
- package/utils/parsing.js +109 -32
- package/utils/upload.js +17 -6
package/client.js
CHANGED
|
@@ -25,7 +25,7 @@ const { Gem } = require('./types/gem');
|
|
|
25
25
|
const { getAccessToken, cookieStr, parseCookies, parseProxy } = require('./utils/accessToken');
|
|
26
26
|
const { rotate1psidts } = require('./utils/rotate');
|
|
27
27
|
const { uploadFile, parseFileName } = require('./utils/upload');
|
|
28
|
-
const { getDeltaByFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse } = require('./utils/parsing');
|
|
28
|
+
const { getDeltaByFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse, StreamingFrameParser } = require('./utils/parsing');
|
|
29
29
|
const { extractDeepResearchPlan } = require('./utils/research');
|
|
30
30
|
|
|
31
31
|
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
@@ -66,10 +66,16 @@ class GeminiClient {
|
|
|
66
66
|
this.watchdogTimeout = 30000;
|
|
67
67
|
this.verbose = false;
|
|
68
68
|
this._running = false;
|
|
69
|
+
this._sessionid = uuidv4().toUpperCase();
|
|
69
70
|
this._reqid = Math.floor(Math.random() * 90000) + 10000;
|
|
70
71
|
this._modelRegistry = {};
|
|
71
72
|
this._recentChats = null;
|
|
72
73
|
this._gems = null;
|
|
74
|
+
this._quotas = {};
|
|
75
|
+
this._usageInfo = {};
|
|
76
|
+
this._abuseStatus = null;
|
|
77
|
+
this._lastActivityTime = 0;
|
|
78
|
+
this._activityTask = null;
|
|
73
79
|
|
|
74
80
|
if (secure_1psid) {
|
|
75
81
|
this.cookies['__Secure-1PSID'] = secure_1psid;
|
|
@@ -102,6 +108,7 @@ class GeminiClient {
|
|
|
102
108
|
this.pushId = pushId || 'feeds/mcudyrk2a4khkz';
|
|
103
109
|
this.cookies = validCookies;
|
|
104
110
|
this._running = true;
|
|
111
|
+
this._sessionid = uuidv4().toUpperCase();
|
|
105
112
|
this._reqid = Math.floor(Math.random() * 90000) + 10000;
|
|
106
113
|
this.timeout = timeout;
|
|
107
114
|
this.autoClose = autoClose;
|
|
@@ -117,6 +124,9 @@ class GeminiClient {
|
|
|
117
124
|
|
|
118
125
|
await this._initRpc();
|
|
119
126
|
|
|
127
|
+
if (this._activityTask) { clearInterval(this._activityTask); this._activityTask = null; }
|
|
128
|
+
if (autoRefresh && this._checkAccountStatus()) this._startActivityWatchdog();
|
|
129
|
+
|
|
120
130
|
if (this.verbose) console.log('Gemini client initialized successfully.');
|
|
121
131
|
} catch (e) { await this.close(); throw e; }
|
|
122
132
|
}
|
|
@@ -126,32 +136,68 @@ class GeminiClient {
|
|
|
126
136
|
this._running = false;
|
|
127
137
|
if (this.closeTask) { clearTimeout(this.closeTask); this.closeTask = null; }
|
|
128
138
|
if (this.refreshTask) { clearInterval(this.refreshTask); this.refreshTask = null; }
|
|
139
|
+
if (this._activityTask) { clearInterval(this._activityTask); this._activityTask = null; }
|
|
129
140
|
}
|
|
130
141
|
|
|
142
|
+
get quotas() { return this._quotas; }
|
|
143
|
+
get usageInfo() { return this._usageInfo; }
|
|
144
|
+
get abuseStatus() { return this._abuseStatus; }
|
|
145
|
+
|
|
131
146
|
_resetCloseTask() {
|
|
132
147
|
if (this.closeTask) { clearTimeout(this.closeTask); this.closeTask = null; }
|
|
133
148
|
this.closeTask = setTimeout(() => this.close(), this.closeDelay);
|
|
134
149
|
}
|
|
135
150
|
|
|
136
151
|
_startAutoRefresh() {
|
|
137
|
-
const
|
|
138
|
-
|
|
152
|
+
const baseInterval = Math.max(this.refreshInterval, 60000);
|
|
153
|
+
const jitter = () => (Math.random() - 0.5) * 30000;
|
|
154
|
+
const scheduleNext = () => {
|
|
139
155
|
if (!this._running) return;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
156
|
+
this.refreshTask = setTimeout(async () => {
|
|
157
|
+
if (!this._running) return;
|
|
158
|
+
try {
|
|
159
|
+
const [new1psidts, rotatedCookies] = await rotate1psidts(this.cookies, this.proxy);
|
|
160
|
+
if (rotatedCookies) Object.assign(this.cookies, rotatedCookies);
|
|
161
|
+
if (!new1psidts) console.warn('Rotation response did not contain a new __Secure-1PSIDTS.');
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.warn(`Unexpected error while refreshing cookies: ${e.message}`);
|
|
164
|
+
}
|
|
165
|
+
scheduleNext();
|
|
166
|
+
}, Math.max(60000, baseInterval + jitter()));
|
|
167
|
+
};
|
|
168
|
+
scheduleNext();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
_startActivityWatchdog() {
|
|
172
|
+
const scheduleNext = () => {
|
|
173
|
+
if (!this._running) return;
|
|
174
|
+
const interval = 60000 + Math.random() * 240000;
|
|
175
|
+
this._activityTask = setTimeout(async () => {
|
|
176
|
+
if (!this._running) return;
|
|
177
|
+
if (!this._checkAccountStatus()) return;
|
|
178
|
+
try {
|
|
179
|
+
await this._syncActivity();
|
|
180
|
+
} catch {}
|
|
181
|
+
scheduleNext();
|
|
182
|
+
}, interval);
|
|
183
|
+
};
|
|
184
|
+
scheduleNext();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
_checkAccountStatus() {
|
|
188
|
+
return this.accountStatus === AccountStatus.AVAILABLE ||
|
|
189
|
+
this.accountStatus === AccountStatus.ACCOUNT_UNTRUSTED;
|
|
148
190
|
}
|
|
149
191
|
|
|
150
192
|
async _initRpc() {
|
|
151
193
|
await this._fetchUserStatus();
|
|
152
|
-
await this.
|
|
153
|
-
await this.
|
|
194
|
+
await this._fetchPreferences();
|
|
195
|
+
await this._syncActivity();
|
|
154
196
|
await this._fetchRecentChats();
|
|
197
|
+
await this._fetchQuota();
|
|
198
|
+
await this._fetchExtraQuota();
|
|
199
|
+
await this._fetchAbuseStatus();
|
|
200
|
+
await this._fetchUsageInfo();
|
|
155
201
|
}
|
|
156
202
|
|
|
157
203
|
async _fetchUserStatus() {
|
|
@@ -195,6 +241,7 @@ class GeminiClient {
|
|
|
195
241
|
})();
|
|
196
242
|
const [capacity, capacityField] = AvailableModel.computeCapacity(tierFlags, capabilityFlags);
|
|
197
243
|
const idNameMapping = AvailableModel.buildModelIdNameMapping();
|
|
244
|
+
const idNumberMapping = AvailableModel.buildModelIdNumberMapping();
|
|
198
245
|
|
|
199
246
|
for (const modelData of modelsList) {
|
|
200
247
|
if (!Array.isArray(modelData)) continue;
|
|
@@ -216,6 +263,7 @@ class GeminiClient {
|
|
|
216
263
|
description,
|
|
217
264
|
capacity,
|
|
218
265
|
capacity_field: capacityField,
|
|
266
|
+
model_number: idNumberMapping[modelId] || 1,
|
|
219
267
|
is_available: isModelAvailable,
|
|
220
268
|
});
|
|
221
269
|
this._modelRegistry[modelId] = model;
|
|
@@ -228,18 +276,178 @@ class GeminiClient {
|
|
|
228
276
|
}
|
|
229
277
|
}
|
|
230
278
|
|
|
231
|
-
async
|
|
279
|
+
async _fetchPreferences() {
|
|
232
280
|
await this._batchExecute([
|
|
233
|
-
new RPCData({ rpcid: GRPC.
|
|
281
|
+
new RPCData({ rpcid: GRPC.READ_USER_PREFERENCES, payload: BARD_SETTINGS_PAYLOAD }),
|
|
234
282
|
]);
|
|
235
283
|
}
|
|
236
284
|
|
|
237
|
-
async
|
|
285
|
+
async _syncActivity() {
|
|
286
|
+
this._lastActivityTime = Date.now();
|
|
287
|
+
if (!this._checkAccountStatus()) return;
|
|
238
288
|
await this._batchExecute([
|
|
239
|
-
new RPCData({ rpcid: GRPC.
|
|
289
|
+
new RPCData({ rpcid: GRPC.READ_USER_PREFERENCES, payload: '[[["bard_activity_enabled"]]]' }),
|
|
240
290
|
]);
|
|
241
291
|
}
|
|
242
292
|
|
|
293
|
+
async _fetchQuota() {
|
|
294
|
+
if (!this._checkAccountStatus()) return;
|
|
295
|
+
const payloads = [
|
|
296
|
+
{ key: 'flash', payload: '[[[1,11],[2,11],[6,11]]]' },
|
|
297
|
+
{ key: 'advanced', payload: '[[[1,4],[6,6],[1,15]]]' },
|
|
298
|
+
];
|
|
299
|
+
const actionLabels = { 4: 'Gemini Pro', 11: 'Gemini Flash', 15: 'Gemini Flash Thinking' };
|
|
300
|
+
for (const { key, payload } of payloads) {
|
|
301
|
+
try {
|
|
302
|
+
const res = await this._batchExecute([
|
|
303
|
+
new RPCData({ rpcid: GRPC.CHECK_GEMINI_QUOTA, payload }),
|
|
304
|
+
]);
|
|
305
|
+
const parts = extractJsonFromResponse(res.data);
|
|
306
|
+
for (const part of parts) {
|
|
307
|
+
const bodyStr = getNestedValue(part, [2]);
|
|
308
|
+
if (!bodyStr) continue;
|
|
309
|
+
let body;
|
|
310
|
+
try { body = JSON.parse(bodyStr); } catch { continue; }
|
|
311
|
+
const items = getNestedValue(body, [0]);
|
|
312
|
+
if (!Array.isArray(items)) continue;
|
|
313
|
+
for (const item of items) {
|
|
314
|
+
const quotaIdList = getNestedValue(item, [0], []);
|
|
315
|
+
const actionId = getNestedValue(item, [0, 1]);
|
|
316
|
+
const usageLevel = getNestedValue(item, [2]);
|
|
317
|
+
const resetTs = getNestedValue(item, [3, 0]);
|
|
318
|
+
const total = getNestedValue(item, [4]);
|
|
319
|
+
const remaining = getNestedValue(item, [5]);
|
|
320
|
+
const quotaId = quotaIdList.join('-');
|
|
321
|
+
const label = actionLabels[actionId] || `Gemini ${key}`;
|
|
322
|
+
this._quotas[quotaId] = { usage_percentage: usageLevel, reset_time: resetTs, total, remaining, action_id: actionId, label: `${label} [${quotaId}]` };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
} catch {}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async _fetchExtraQuota() {
|
|
330
|
+
try {
|
|
331
|
+
const res = await this._batchExecute([
|
|
332
|
+
new RPCData({ rpcid: GRPC.CHECK_QUOTA, payload: '[]' }),
|
|
333
|
+
]);
|
|
334
|
+
const parts = extractJsonFromResponse(res.data);
|
|
335
|
+
for (const part of parts) {
|
|
336
|
+
const bodyStr = getNestedValue(part, [2]);
|
|
337
|
+
if (!bodyStr) continue;
|
|
338
|
+
let body;
|
|
339
|
+
try { body = JSON.parse(bodyStr); } catch { continue; }
|
|
340
|
+
const isBlocked = getNestedValue(body, [0]);
|
|
341
|
+
const usageLevel = getNestedValue(body, [1]);
|
|
342
|
+
const resetTs = getNestedValue(body, [2, 0]);
|
|
343
|
+
if (!this._quotas.extra) this._quotas.extra = {};
|
|
344
|
+
this._quotas.extra.default = {
|
|
345
|
+
is_blocked: isBlocked,
|
|
346
|
+
usage_percentage: typeof usageLevel === 'number' ? usageLevel * 100 : null,
|
|
347
|
+
reset_time: resetTs,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
} catch {}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async _fetchAbuseStatus() {
|
|
354
|
+
try {
|
|
355
|
+
const res = await this._batchExecute([
|
|
356
|
+
new RPCData({ rpcid: GRPC.GET_ABUSE_STATUS, payload: '[]' }),
|
|
357
|
+
]);
|
|
358
|
+
const parts = extractJsonFromResponse(res.data);
|
|
359
|
+
for (const part of parts) {
|
|
360
|
+
const bodyStr = getNestedValue(part, [2]);
|
|
361
|
+
if (!bodyStr) continue;
|
|
362
|
+
let body;
|
|
363
|
+
try { body = JSON.parse(bodyStr); } catch { continue; }
|
|
364
|
+
const abuseInfo = getNestedValue(body, [1]);
|
|
365
|
+
if (!abuseInfo) {
|
|
366
|
+
this._abuseStatus = { is_clean: true, status_code: null, signal: null };
|
|
367
|
+
} else {
|
|
368
|
+
const rawStatus = getNestedValue(abuseInfo, [1]);
|
|
369
|
+
const signal = getNestedValue(abuseInfo, [3, 1]);
|
|
370
|
+
const statusCode = typeof rawStatus === 'number' ? Math.floor(rawStatus / 1000000) : null;
|
|
371
|
+
this._abuseStatus = { is_clean: false, status_code: statusCode, signal };
|
|
372
|
+
if (this.verbose) console.warn(`Potential account restriction detected: ${JSON.stringify(this._abuseStatus)}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
} catch {}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async _fetchUsageInfo() {
|
|
379
|
+
if (!this._checkAccountStatus()) return;
|
|
380
|
+
const tierLabels = { 1: 'FREE', 2: 'PRO', 3: 'ULTRA', 4: 'PLUS', 6: 'ULTRA' };
|
|
381
|
+
const metricWindows = { 1: ['current_5h', '5h'], 2: ['weekly', 'weekly'] };
|
|
382
|
+
try {
|
|
383
|
+
const res = await this._batchExecute([
|
|
384
|
+
new RPCData({ rpcid: GRPC.GET_USAGE_INFO, payload: '[]' }),
|
|
385
|
+
], 2, true, '/usage');
|
|
386
|
+
const parts = extractJsonFromResponse(res.data);
|
|
387
|
+
for (const part of parts) {
|
|
388
|
+
const bodyStr = getNestedValue(part, [2]);
|
|
389
|
+
if (!bodyStr) continue;
|
|
390
|
+
let body;
|
|
391
|
+
try { body = JSON.parse(bodyStr); } catch { continue; }
|
|
392
|
+
const tierId = getNestedValue(body, [0]);
|
|
393
|
+
const usageItems = getNestedValue(body, [1], []);
|
|
394
|
+
const useOverage = getNestedValue(body, [2]);
|
|
395
|
+
const info = {
|
|
396
|
+
tier: { id: tierId, label: tierLabels[tierId] || null },
|
|
397
|
+
use_overage_ai_credits: useOverage,
|
|
398
|
+
current_5h: null,
|
|
399
|
+
weekly: null,
|
|
400
|
+
};
|
|
401
|
+
if (Array.isArray(usageItems)) {
|
|
402
|
+
for (const item of usageItems) {
|
|
403
|
+
const remaining = getNestedValue(item, [0]);
|
|
404
|
+
const usageLevel = getNestedValue(item, [1]);
|
|
405
|
+
const metricType = getNestedValue(item, [2]);
|
|
406
|
+
const resetTs = getNestedValue(item, [3, 0, 0]);
|
|
407
|
+
if (metricType === 3) { info.ai_credits_remaining = remaining; continue; }
|
|
408
|
+
const [metricLabel] = metricWindows[metricType] || [`type_${metricType}`];
|
|
409
|
+
info[metricLabel] = {
|
|
410
|
+
type: metricType,
|
|
411
|
+
remaining_credits: remaining,
|
|
412
|
+
usage_level: usageLevel,
|
|
413
|
+
usage_percentage: typeof usageLevel === 'number' ? Math.round(usageLevel * 100) : null,
|
|
414
|
+
reset_at: resetTs,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
this._usageInfo = info;
|
|
419
|
+
this._quotas.usage_info = info;
|
|
420
|
+
}
|
|
421
|
+
} catch {}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
_parseRpcResults(responseText, targetId) {
|
|
425
|
+
const results = [];
|
|
426
|
+
try {
|
|
427
|
+
const parts = extractJsonFromResponse(responseText);
|
|
428
|
+
for (const part of parts) {
|
|
429
|
+
if (getNestedValue(part, [1]) !== targetId) continue;
|
|
430
|
+
const rejectCode = getNestedValue(part, [5, 0]);
|
|
431
|
+
if (rejectCode === 7) {
|
|
432
|
+
this.accountStatus = AccountStatus.UNAUTHENTICATED;
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
const bodyStr = getNestedValue(part, [2]);
|
|
436
|
+
if (!bodyStr) continue;
|
|
437
|
+
try { results.push(JSON.parse(bodyStr)); } catch {}
|
|
438
|
+
}
|
|
439
|
+
} catch {}
|
|
440
|
+
return results;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async _sendBardSettings() {
|
|
444
|
+
await this._fetchPreferences();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async _sendBardActivity() {
|
|
448
|
+
await this._syncActivity();
|
|
449
|
+
}
|
|
450
|
+
|
|
243
451
|
listModels() {
|
|
244
452
|
const vals = Object.values(this._modelRegistry);
|
|
245
453
|
return vals.length > 0 ? vals : null;
|
|
@@ -247,8 +455,9 @@ class GeminiClient {
|
|
|
247
455
|
|
|
248
456
|
_resolveModelByName(name) {
|
|
249
457
|
if (name in this._modelRegistry) return this._modelRegistry[name];
|
|
458
|
+
const lower = name.toLowerCase();
|
|
250
459
|
for (const m of Object.values(this._modelRegistry)) {
|
|
251
|
-
if (m.model_name ===
|
|
460
|
+
if (m.model_name.toLowerCase() === lower || m.display_name.toLowerCase() === lower) return m;
|
|
252
461
|
}
|
|
253
462
|
return Model.fromName(name);
|
|
254
463
|
}
|
|
@@ -280,7 +489,7 @@ class GeminiClient {
|
|
|
280
489
|
return model;
|
|
281
490
|
}
|
|
282
491
|
|
|
283
|
-
async _batchExecute(payloads, retries = 2, closeOnError = true) {
|
|
492
|
+
async _batchExecute(payloads, retries = 2, closeOnError = true, sourcePath = '/app') {
|
|
284
493
|
let lastErr;
|
|
285
494
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
286
495
|
try {
|
|
@@ -292,7 +501,7 @@ class GeminiClient {
|
|
|
292
501
|
hl: this.language || 'en',
|
|
293
502
|
_reqid: String(_reqid),
|
|
294
503
|
rt: 'c',
|
|
295
|
-
'source-path':
|
|
504
|
+
'source-path': sourcePath,
|
|
296
505
|
});
|
|
297
506
|
if (this.buildLabel) params.set('bl', this.buildLabel);
|
|
298
507
|
if (this.sessionId) params.set('f.sid', this.sessionId);
|
|
@@ -340,14 +549,27 @@ class GeminiClient {
|
|
|
340
549
|
chat = null,
|
|
341
550
|
temporary = false,
|
|
342
551
|
deep_research = false,
|
|
552
|
+
extended_thinking = false,
|
|
343
553
|
} = {}) {
|
|
554
|
+
if (!this._running) {
|
|
555
|
+
await this.init({
|
|
556
|
+
timeout: this.timeout,
|
|
557
|
+
autoClose: this.autoClose,
|
|
558
|
+
closeDelay: this.closeDelay,
|
|
559
|
+
autoRefresh: this.autoRefresh,
|
|
560
|
+
refreshInterval: this.refreshInterval,
|
|
561
|
+
verbose: this.verbose,
|
|
562
|
+
watchdogTimeout: this.watchdogTimeout,
|
|
563
|
+
});
|
|
564
|
+
if (!this._running) throw new APIError(`Invalid function call: GeminiClient.generateContent. Client initialization failed.`);
|
|
565
|
+
}
|
|
344
566
|
if (this.autoClose) this._resetCloseTask();
|
|
345
567
|
|
|
346
568
|
let fileData = null;
|
|
347
569
|
if (files && files.length) {
|
|
348
570
|
await this._sendBardActivity();
|
|
349
571
|
const uploaded = await Promise.all(
|
|
350
|
-
files.map(f => uploadFile(f, this.proxy, this.pushId)),
|
|
572
|
+
files.map(f => uploadFile(f, this.proxy, this.pushId, this.cookies)),
|
|
351
573
|
);
|
|
352
574
|
fileData = uploaded.map((url, i) => [[url], parseFileName(files[i])]);
|
|
353
575
|
}
|
|
@@ -357,7 +579,7 @@ class GeminiClient {
|
|
|
357
579
|
const ss = { last_texts: {}, last_thoughts: {}, last_progress_time: Date.now(), is_thinking: false, is_queueing: false };
|
|
358
580
|
let output = null;
|
|
359
581
|
for await (const out of this._generate({
|
|
360
|
-
prompt, fileData, model, gem, chat, temporary, ss, deep_research,
|
|
582
|
+
prompt, fileData, model, gem, chat, temporary, ss, deep_research, extended_thinking,
|
|
361
583
|
})) output = out;
|
|
362
584
|
if (!output) throw new GeminiError('Failed to generate contents. No output data found in response.');
|
|
363
585
|
if (chat instanceof ChatSession) { output.metadata = chat.metadata; chat.lastOutput = output; }
|
|
@@ -374,14 +596,27 @@ class GeminiClient {
|
|
|
374
596
|
chat = null,
|
|
375
597
|
temporary = false,
|
|
376
598
|
deep_research = false,
|
|
599
|
+
extended_thinking = false,
|
|
377
600
|
} = {}) {
|
|
601
|
+
if (!this._running) {
|
|
602
|
+
await this.init({
|
|
603
|
+
timeout: this.timeout,
|
|
604
|
+
autoClose: this.autoClose,
|
|
605
|
+
closeDelay: this.closeDelay,
|
|
606
|
+
autoRefresh: this.autoRefresh,
|
|
607
|
+
refreshInterval: this.refreshInterval,
|
|
608
|
+
verbose: this.verbose,
|
|
609
|
+
watchdogTimeout: this.watchdogTimeout,
|
|
610
|
+
});
|
|
611
|
+
if (!this._running) throw new APIError(`Invalid function call: GeminiClient.generateContentStream. Client initialization failed.`);
|
|
612
|
+
}
|
|
378
613
|
if (this.autoClose) this._resetCloseTask();
|
|
379
614
|
|
|
380
615
|
let fileData = null;
|
|
381
616
|
if (files && files.length) {
|
|
382
617
|
await this._sendBardActivity();
|
|
383
618
|
const uploaded = await Promise.all(
|
|
384
|
-
files.map(f => uploadFile(f, this.proxy, this.pushId)),
|
|
619
|
+
files.map(f => uploadFile(f, this.proxy, this.pushId, this.cookies)),
|
|
385
620
|
);
|
|
386
621
|
fileData = uploaded.map((url, i) => [[url], parseFileName(files[i])]);
|
|
387
622
|
}
|
|
@@ -390,7 +625,7 @@ class GeminiClient {
|
|
|
390
625
|
const ss = { last_texts: {}, last_thoughts: {} };
|
|
391
626
|
let output = null;
|
|
392
627
|
for await (const out of this._generate({
|
|
393
|
-
prompt, fileData, model, gem, chat, temporary, ss, deep_research,
|
|
628
|
+
prompt, fileData, model, gem, chat, temporary, ss, deep_research, extended_thinking,
|
|
394
629
|
})) {
|
|
395
630
|
output = out;
|
|
396
631
|
yield out;
|
|
@@ -407,6 +642,7 @@ class GeminiClient {
|
|
|
407
642
|
temporary = false,
|
|
408
643
|
ss = null,
|
|
409
644
|
deep_research = false,
|
|
645
|
+
extended_thinking = false,
|
|
410
646
|
}, retries = 5) {
|
|
411
647
|
if (!prompt) throw new Error('Prompt cannot be empty.');
|
|
412
648
|
|
|
@@ -414,7 +650,7 @@ class GeminiClient {
|
|
|
414
650
|
|
|
415
651
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
416
652
|
try {
|
|
417
|
-
for await (const out of this._stream({ prompt, fileData, model, gem, chat, temporary, ss, deep_research })) yield out;
|
|
653
|
+
for await (const out of this._stream({ prompt, fileData, model, gem, chat, temporary, ss, deep_research, extended_thinking })) yield out;
|
|
418
654
|
return;
|
|
419
655
|
} catch (e) {
|
|
420
656
|
if (
|
|
@@ -427,7 +663,7 @@ class GeminiClient {
|
|
|
427
663
|
}
|
|
428
664
|
}
|
|
429
665
|
|
|
430
|
-
async* _stream({ prompt, fileData = null, model = Model.UNSPECIFIED, gem = null, chat = null, temporary = false, ss = null, deep_research = false }) {
|
|
666
|
+
async* _stream({ prompt, fileData = null, model = Model.UNSPECIFIED, gem = null, chat = null, temporary = false, ss = null, deep_research = false, extended_thinking = false }) {
|
|
431
667
|
const _reqid = this._reqid;
|
|
432
668
|
this._reqid += 100000;
|
|
433
669
|
const gemId = gem instanceof Gem ? gem.id : gem;
|
|
@@ -439,7 +675,7 @@ class GeminiClient {
|
|
|
439
675
|
rcid: chat.rcid,
|
|
440
676
|
} : null;
|
|
441
677
|
|
|
442
|
-
const inner = new Array(
|
|
678
|
+
const inner = new Array(81).fill(null);
|
|
443
679
|
inner[0] = [prompt, 0, null, fileData, null, null, 0];
|
|
444
680
|
inner[1] = [this.language || 'en'];
|
|
445
681
|
inner[2] = chat instanceof ChatSession ? chat.metadata : [...DEFAULT_METADATA];
|
|
@@ -465,11 +701,24 @@ class GeminiClient {
|
|
|
465
701
|
inner[55] = [[1]];
|
|
466
702
|
}
|
|
467
703
|
inner[61] = [];
|
|
468
|
-
inner[68] =
|
|
704
|
+
inner[68] = 1;
|
|
705
|
+
inner[80] = extended_thinking ? 2 : 1;
|
|
469
706
|
|
|
470
707
|
const uid = uuidv4().toUpperCase();
|
|
471
708
|
inner[59] = uid;
|
|
472
709
|
|
|
710
|
+
const modelHeaders = { ...model.model_header };
|
|
711
|
+
if (MODEL_HEADER_KEY in modelHeaders) {
|
|
712
|
+
try {
|
|
713
|
+
const parsed = JSON.parse(modelHeaders[MODEL_HEADER_KEY]);
|
|
714
|
+
const modelNumber = typeof parsed[parsed.length - 1] === 'number' ? parsed[parsed.length - 1] : null;
|
|
715
|
+
if (typeof modelNumber === 'number') inner[79] = modelNumber;
|
|
716
|
+
parsed.push(extended_thinking ? 2 : 1);
|
|
717
|
+
parsed.push(this.sessionId || null);
|
|
718
|
+
modelHeaders[MODEL_HEADER_KEY] = JSON.stringify(parsed);
|
|
719
|
+
} catch {}
|
|
720
|
+
}
|
|
721
|
+
|
|
473
722
|
const params = new URLSearchParams({
|
|
474
723
|
hl: this.language || 'en',
|
|
475
724
|
_reqid: String(_reqid),
|
|
@@ -492,7 +741,7 @@ class GeminiClient {
|
|
|
492
741
|
{
|
|
493
742
|
headers: {
|
|
494
743
|
...Headers.GEMINI,
|
|
495
|
-
...
|
|
744
|
+
...modelHeaders,
|
|
496
745
|
'x-goog-ext-525005358-jspb': `["${uid}",1]`,
|
|
497
746
|
...Headers.SAME_DOMAIN,
|
|
498
747
|
'Cookie': cookieStr(this.cookies),
|
|
@@ -517,7 +766,7 @@ class GeminiClient {
|
|
|
517
766
|
let isCompleted = false, isFinalChunk = false;
|
|
518
767
|
let cid = chat instanceof ChatSession ? chat.cid : '';
|
|
519
768
|
let rid = chat instanceof ChatSession ? chat.rid : '';
|
|
520
|
-
|
|
769
|
+
const frameParser = new StreamingFrameParser();
|
|
521
770
|
|
|
522
771
|
const processParts = (parts) => {
|
|
523
772
|
const outs = [];
|
|
@@ -536,7 +785,7 @@ class GeminiClient {
|
|
|
536
785
|
case ErrorCode.TEMPORARY_ERROR_1013:
|
|
537
786
|
throw new APIError('Temporary error (1013). Retrying...');
|
|
538
787
|
default:
|
|
539
|
-
throw new APIError(`Unknown API error code: ${ec}.`);
|
|
788
|
+
throw new APIError(`Failed to generate contents. Unknown API error code: ${ec}. This might be a content policy rejection or a temporary Google service issue.`);
|
|
540
789
|
}
|
|
541
790
|
}
|
|
542
791
|
|
|
@@ -652,10 +901,7 @@ class GeminiClient {
|
|
|
652
901
|
|
|
653
902
|
res.data.on('data', chunk => {
|
|
654
903
|
try {
|
|
655
|
-
|
|
656
|
-
if (buf.startsWith(")]}'")) buf = buf.slice(4).trimStart();
|
|
657
|
-
const [parts, rem] = parseResponseByFrame(buf);
|
|
658
|
-
buf = rem;
|
|
904
|
+
const parts = frameParser.feed(chunk.toString('utf8'));
|
|
659
905
|
const outs = processParts(parts);
|
|
660
906
|
for (const o of outs) yielded.push(o);
|
|
661
907
|
if (outs.length || isThinking || isQueueing) lastProg = Date.now();
|
|
@@ -668,10 +914,8 @@ class GeminiClient {
|
|
|
668
914
|
res.data.on('end', () => {
|
|
669
915
|
clearInterval(watchdog);
|
|
670
916
|
try {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
for (const o of processParts(p)) yielded.push(o);
|
|
674
|
-
}
|
|
917
|
+
const remaining = frameParser.flush();
|
|
918
|
+
for (const o of processParts(remaining)) yielded.push(o);
|
|
675
919
|
if (!isCompleted && !isFinalChunk) reject(new APIError('Stream interrupted or truncated.'));
|
|
676
920
|
else resolve();
|
|
677
921
|
} catch (e) { reject(e); }
|
|
@@ -899,7 +1143,7 @@ class ChatSession {
|
|
|
899
1143
|
get rcid() { return this._metadata[2]; }
|
|
900
1144
|
set rcid(v) { this._metadata[2] = v; }
|
|
901
1145
|
|
|
902
|
-
async sendMessage({ prompt, files = null, temporary = false, deep_research = false } = {}) {
|
|
1146
|
+
async sendMessage({ prompt, files = null, temporary = false, deep_research = false, extended_thinking = false } = {}) {
|
|
903
1147
|
return this.geminiclient.generateContent({
|
|
904
1148
|
prompt,
|
|
905
1149
|
files,
|
|
@@ -908,10 +1152,11 @@ class ChatSession {
|
|
|
908
1152
|
chat: this,
|
|
909
1153
|
temporary,
|
|
910
1154
|
deep_research,
|
|
1155
|
+
extended_thinking,
|
|
911
1156
|
});
|
|
912
1157
|
}
|
|
913
1158
|
|
|
914
|
-
async* sendMessageStream({ prompt, files = null, temporary = false, deep_research = false } = {}) {
|
|
1159
|
+
async* sendMessageStream({ prompt, files = null, temporary = false, deep_research = false, extended_thinking = false } = {}) {
|
|
915
1160
|
for await (const out of this.geminiclient.generateContentStream({
|
|
916
1161
|
prompt,
|
|
917
1162
|
files,
|
|
@@ -920,6 +1165,7 @@ class ChatSession {
|
|
|
920
1165
|
chat: this,
|
|
921
1166
|
temporary,
|
|
922
1167
|
deep_research,
|
|
1168
|
+
extended_thinking,
|
|
923
1169
|
})) yield out;
|
|
924
1170
|
}
|
|
925
1171
|
|
package/components/chatMixin.js
CHANGED
|
@@ -14,6 +14,7 @@ class ChatMixin {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
async _fetchRecentChats(recent = 13) {
|
|
17
|
+
if (!this._checkAccountStatus()) return;
|
|
17
18
|
const fetchBatch = async (payload) => {
|
|
18
19
|
return this._batchExecute([
|
|
19
20
|
new RPCData({ rpcid: GRPC.LIST_CHATS, payload: JSON.stringify([recent, null, payload]) }),
|
package/components/gemMixin.js
CHANGED
|
@@ -16,7 +16,8 @@ class GemMixin {
|
|
|
16
16
|
return this._gems;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async fetchGems({ includeHidden = false
|
|
19
|
+
async fetchGems({ includeHidden = false } = {}) {
|
|
20
|
+
const language = this.language || 'en';
|
|
20
21
|
const response = await this._batchExecute([
|
|
21
22
|
new RPCData({ rpcid: GRPC.LIST_GEMS, payload: includeHidden ? `[4,['${language}'],0]` : `[3,['${language}'],0]`, identifier: 'system' }),
|
|
22
23
|
new RPCData({ rpcid: GRPC.LIST_GEMS, payload: `[2,['${language}'],0]`, identifier: 'custom' }),
|