gemini-reverse 1.0.6 → 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 +260 -38
- package/components/chatMixin.js +1 -0
- package/constants.js +59 -23
- package/index.d.ts +9 -1
- package/index.js +2 -0
- package/package.json +1 -1
- package/types/availablemodel.js +32 -5
- package/utils/index.js +2 -1
- package/utils/parsing.js +109 -32
package/README.md
CHANGED
|
@@ -6,15 +6,18 @@ An unofficial Node.js client for [Google Gemini](https://gemini.google.com), ins
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **Persistent Cookies** — Automatically refreshes cookies in the background. Optimized for always-on services.
|
|
9
|
+
- **Persistent Cookies** — Automatically refreshes cookies in the background with jitter to prevent synchronized requests. Optimized for always-on services.
|
|
10
10
|
- **Image Generation** — Natively supports generating and editing images with natural language.
|
|
11
11
|
- **Video & Audio Generation** — Supports generating videos and audio/music content natively.
|
|
12
12
|
- **Deep Research** — Full deep research workflow with plan creation, status polling, and result retrieval.
|
|
13
|
+
- **Extended Thinking** — Enables deeper reasoning mode on supported models.
|
|
13
14
|
- **System Prompt** — Supports customizing the model's system prompt with [Gemini Gems](https://gemini.google.com/gems/view).
|
|
14
15
|
- **Extension Support** — Supports generating content with Gemini extensions such as YouTube and Gmail.
|
|
15
16
|
- **Classified Outputs** — Categorizes text, thoughts, images, videos, and audio in the response.
|
|
16
|
-
- **Streaming Mode** — Supports stream generation, yielding partial outputs as they are generated.
|
|
17
|
+
- **Streaming Mode** — Supports stream generation with an incremental stateful frame parser, yielding partial outputs as they are generated.
|
|
17
18
|
- **Dynamic Model Discovery** — Automatically discovers available models from your account at initialization.
|
|
19
|
+
- **Quota & Usage Info** — Exposes account quota, compute usage, and abuse status after initialization.
|
|
20
|
+
- **Activity Watchdog** — Background heartbeat task that keeps the session alive automatically.
|
|
18
21
|
- **TypeScript Support** — Full TypeScript type declarations included out of the box.
|
|
19
22
|
|
|
20
23
|
## Table of Contents
|
|
@@ -34,6 +37,7 @@ An unofficial Node.js client for [Google Gemini](https://gemini.google.com), ins
|
|
|
34
37
|
- [Delete a Conversation](#delete-a-conversation)
|
|
35
38
|
- [Temporary Mode](#temporary-mode)
|
|
36
39
|
- [Streaming Mode](#streaming-mode)
|
|
40
|
+
- [Extended Thinking](#extended-thinking)
|
|
37
41
|
- [Select Language Model](#select-language-model)
|
|
38
42
|
- [List Available Models](#list-available-models)
|
|
39
43
|
- [Apply System Prompt with Gemini Gems](#apply-system-prompt-with-gemini-gems)
|
|
@@ -49,6 +53,7 @@ An unofficial Node.js client for [Google Gemini](https://gemini.google.com), ins
|
|
|
49
53
|
- [Check and Switch to Other Reply Candidates](#check-and-switch-to-other-reply-candidates)
|
|
50
54
|
- [Deep Research](#deep-research)
|
|
51
55
|
- [Account Status](#account-status)
|
|
56
|
+
- [Quota and Usage Info](#quota-and-usage-info)
|
|
52
57
|
- [Error Handling](#error-handling)
|
|
53
58
|
- [Cookie Persistence](#cookie-persistence)
|
|
54
59
|
- [TypeScript](#typescript)
|
|
@@ -73,7 +78,7 @@ npm install gemini-reverse
|
|
|
73
78
|
|
|
74
79
|
### Initialization
|
|
75
80
|
|
|
76
|
-
Import the package and initialize a client with your cookies. After a successful initialization, the client will automatically refresh `__Secure-1PSIDTS` in the background
|
|
81
|
+
Import the package and initialize a client with your cookies. After a successful initialization, the client will automatically refresh `__Secure-1PSIDTS` in the background with random jitter, and start a heartbeat watchdog to keep the session alive.
|
|
77
82
|
|
|
78
83
|
```js
|
|
79
84
|
const { GeminiClient } = require('gemini-reverse');
|
|
@@ -88,7 +93,7 @@ await client.init({
|
|
|
88
93
|
timeout: 300000, // request timeout in ms, default 300000
|
|
89
94
|
autoClose: false, // auto-close client after inactivity
|
|
90
95
|
closeDelay: 300000, // inactivity delay before closing in ms
|
|
91
|
-
autoRefresh: true, // auto-refresh cookies
|
|
96
|
+
autoRefresh: true, // auto-refresh cookies + start activity watchdog
|
|
92
97
|
refreshInterval: 540000 // cookie refresh interval in ms
|
|
93
98
|
});
|
|
94
99
|
```
|
|
@@ -215,7 +220,7 @@ console.log(res2.text);
|
|
|
215
220
|
|
|
216
221
|
### Streaming Mode
|
|
217
222
|
|
|
218
|
-
For longer responses, use streaming mode to receive partial outputs as they are generated. The `text_delta` attribute contains only the **new characters** received since the last yield
|
|
223
|
+
For longer responses, use streaming mode to receive partial outputs as they are generated. The response uses a stateful `StreamingFrameParser` internally, so partial frames are buffered efficiently across chunks without rescanning. The `text_delta` attribute contains only the **new characters** received since the last yield.
|
|
219
224
|
|
|
220
225
|
```js
|
|
221
226
|
for await (const chunk of client.generateContentStream({
|
|
@@ -235,6 +240,36 @@ for await (const chunk of chat.sendMessageStream({ prompt: 'Tell me a long story
|
|
|
235
240
|
}
|
|
236
241
|
```
|
|
237
242
|
|
|
243
|
+
### Extended Thinking
|
|
244
|
+
|
|
245
|
+
Pass `extended_thinking: true` to enable deeper reasoning mode. This causes the model to spend more time planning before responding. Supported on Pro and Advanced tier models.
|
|
246
|
+
|
|
247
|
+
```js
|
|
248
|
+
const { Model } = require('gemini-reverse');
|
|
249
|
+
|
|
250
|
+
const response = await client.generateContent({
|
|
251
|
+
prompt: 'Solve this step by step: If a train travels at 120 km/h and needs to cover 450 km, how long does it take?',
|
|
252
|
+
model: Model.ADVANCED_PRO,
|
|
253
|
+
extended_thinking: true,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
if (response.thoughts) {
|
|
257
|
+
console.log('Thinking process:', response.thoughts);
|
|
258
|
+
}
|
|
259
|
+
console.log('Answer:', response.text);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Also works in streaming and chat:
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
const chat = client.startChat({ model: Model.ADVANCED_FLASH });
|
|
266
|
+
const res = await chat.sendMessage({
|
|
267
|
+
prompt: 'Explain the P vs NP problem.',
|
|
268
|
+
extended_thinking: true,
|
|
269
|
+
});
|
|
270
|
+
console.log(res.text);
|
|
271
|
+
```
|
|
272
|
+
|
|
238
273
|
### Select Language Model
|
|
239
274
|
|
|
240
275
|
Specify which language model to use by passing a `model` argument. Available models are discovered dynamically at init time based on your account tier.
|
|
@@ -248,7 +283,7 @@ const response1 = await client.generateContent({
|
|
|
248
283
|
model: Model.BASIC_FLASH,
|
|
249
284
|
});
|
|
250
285
|
|
|
251
|
-
// Using a model name string
|
|
286
|
+
// Using a model name string (case-insensitive)
|
|
252
287
|
const chat = client.startChat({ model: 'gemini-3-pro' });
|
|
253
288
|
|
|
254
289
|
// Using a custom model header dict
|
|
@@ -256,7 +291,9 @@ const chat2 = client.startChat({
|
|
|
256
291
|
model: {
|
|
257
292
|
model_name: 'custom',
|
|
258
293
|
model_header: {
|
|
259
|
-
'x-goog-ext-525001261-jspb': '[1,null,null,null,"MODEL_ID",null,null,0,[4],null,null,
|
|
294
|
+
'x-goog-ext-525001261-jspb': '[1,null,null,null,"MODEL_ID",null,null,0,[4,5,6,8],null,null,1,null,null,1]',
|
|
295
|
+
'x-goog-ext-73010989-jspb': '[0]',
|
|
296
|
+
'x-goog-ext-73010990-jspb': '[0,0,0]',
|
|
260
297
|
},
|
|
261
298
|
},
|
|
262
299
|
});
|
|
@@ -269,13 +306,13 @@ const chat2 = client.startChat({
|
|
|
269
306
|
| `Model.UNSPECIFIED` | `unspecified` | Default, lets Gemini choose |
|
|
270
307
|
| `Model.BASIC_PRO` | `gemini-3-pro` | Free tier |
|
|
271
308
|
| `Model.BASIC_FLASH` | `gemini-3-flash` | Free tier, fastest |
|
|
272
|
-
| `Model.
|
|
309
|
+
| `Model.BASIC_LITE` | `gemini-3-lite` | Free tier, lightweight |
|
|
273
310
|
| `Model.PLUS_PRO` | `gemini-3-pro-plus` | Plus tier |
|
|
274
311
|
| `Model.PLUS_FLASH` | `gemini-3-flash-plus` | Plus tier |
|
|
275
|
-
| `Model.
|
|
312
|
+
| `Model.PLUS_LITE` | `gemini-3-lite-plus` | Plus tier |
|
|
276
313
|
| `Model.ADVANCED_PRO` | `gemini-3-pro-advanced` | Advanced tier |
|
|
277
314
|
| `Model.ADVANCED_FLASH` | `gemini-3-flash-advanced` | Advanced tier |
|
|
278
|
-
| `Model.
|
|
315
|
+
| `Model.ADVANCED_LITE` | `gemini-3-lite-advanced` | Advanced tier |
|
|
279
316
|
|
|
280
317
|
### List Available Models
|
|
281
318
|
|
|
@@ -288,7 +325,7 @@ const models = client.listModels();
|
|
|
288
325
|
if (models) {
|
|
289
326
|
for (const model of models) {
|
|
290
327
|
console.log(`${model.model_id} → ${model.model_name || model.display_name}`);
|
|
291
|
-
console.log(` capacity: ${model.capacity}, advanced_only: ${model.advanced_only}`);
|
|
328
|
+
console.log(` capacity: ${model.capacity}, model_number: ${model.model_number}, advanced_only: ${model.advanced_only}`);
|
|
292
329
|
}
|
|
293
330
|
}
|
|
294
331
|
```
|
|
@@ -377,7 +414,7 @@ When using thinking-capable models, the model's internal reasoning is exposed vi
|
|
|
377
414
|
```js
|
|
378
415
|
const response = await client.generateContent({
|
|
379
416
|
prompt: 'What is 17 × 23?',
|
|
380
|
-
model: Model.
|
|
417
|
+
model: Model.BASIC_FLASH,
|
|
381
418
|
});
|
|
382
419
|
|
|
383
420
|
if (response.thoughts) {
|
|
@@ -582,6 +619,41 @@ if (client.accountStatus === AccountStatus.AVAILABLE) {
|
|
|
582
619
|
| `AccountStatus.GUARDIAN_APPROVAL_REQUIRED` | 1057 | Requires parental approval |
|
|
583
620
|
| `AccountStatus.LOCATION_REJECTED` | 1060 | Not available in your country/region |
|
|
584
621
|
|
|
622
|
+
### Quota and Usage Info
|
|
623
|
+
|
|
624
|
+
After initialization, the client fetches account quota limits and compute usage metrics automatically. Access them via `client.quotas`, `client.usageInfo`, and `client.abuseStatus`.
|
|
625
|
+
|
|
626
|
+
```js
|
|
627
|
+
await client.init();
|
|
628
|
+
|
|
629
|
+
// Quota limits (Flash, Pro, extra features)
|
|
630
|
+
const quotas = client.quotas;
|
|
631
|
+
for (const [id, q] of Object.entries(quotas)) {
|
|
632
|
+
if (q.label) {
|
|
633
|
+
const remaining = q.remaining !== null ? `${q.remaining}/${q.total}` : 'unlimited';
|
|
634
|
+
console.log(`${q.label}: ${remaining} credits (${q.usage_percentage?.toFixed(1) ?? '?'}% used)`);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Compute usage (5-hour and weekly windows)
|
|
639
|
+
const usage = client.usageInfo;
|
|
640
|
+
if (usage.tier) {
|
|
641
|
+
console.log(`Account tier: ${usage.tier.label}`);
|
|
642
|
+
}
|
|
643
|
+
if (usage.current_5h) {
|
|
644
|
+
console.log(`5h window: ${usage.current_5h.remaining_credits} credits remaining (${usage.current_5h.usage_percentage}% used)`);
|
|
645
|
+
}
|
|
646
|
+
if (usage.weekly) {
|
|
647
|
+
console.log(`Weekly: ${usage.weekly.remaining_credits} credits remaining`);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Abuse status
|
|
651
|
+
const abuse = client.abuseStatus;
|
|
652
|
+
if (abuse) {
|
|
653
|
+
console.log(`Account clean: ${abuse.is_clean}`);
|
|
654
|
+
}
|
|
655
|
+
```
|
|
656
|
+
|
|
585
657
|
## Error Handling
|
|
586
658
|
|
|
587
659
|
```js
|
|
@@ -651,13 +723,17 @@ import {
|
|
|
651
723
|
AccountStatus,
|
|
652
724
|
DeepResearchPlan,
|
|
653
725
|
DeepResearchResult,
|
|
726
|
+
StreamingFrameParser,
|
|
654
727
|
} from 'gemini-reverse';
|
|
655
728
|
|
|
656
729
|
const client = new GeminiClient({ secure_1psid: '...' });
|
|
657
730
|
await client.init();
|
|
658
731
|
|
|
659
732
|
const chat: ChatSession = client.startChat({ model: 'gemini-3-flash' });
|
|
660
|
-
const response: ModelOutput = await chat.sendMessage({
|
|
733
|
+
const response: ModelOutput = await chat.sendMessage({
|
|
734
|
+
prompt: 'Hello!',
|
|
735
|
+
extended_thinking: false,
|
|
736
|
+
});
|
|
661
737
|
|
|
662
738
|
console.log(response.text);
|
|
663
739
|
|
|
@@ -667,6 +743,11 @@ if (history) {
|
|
|
667
743
|
}
|
|
668
744
|
|
|
669
745
|
const models: AvailableModel[] | null = client.listModels();
|
|
746
|
+
|
|
747
|
+
// Access quota and usage after init
|
|
748
|
+
console.log(client.quotas);
|
|
749
|
+
console.log(client.usageInfo);
|
|
750
|
+
console.log(client.abuseStatus);
|
|
670
751
|
```
|
|
671
752
|
|
|
672
753
|
## Project Structure
|
|
@@ -692,9 +773,9 @@ gemini-reverse/
|
|
|
692
773
|
│ └── video.js # Video, GeneratedVideo, GeneratedMedia
|
|
693
774
|
├── utils/
|
|
694
775
|
│ ├── accessToken.js # cookie handling & init request
|
|
695
|
-
│ ├── parsing.js # response parsing
|
|
776
|
+
│ ├── parsing.js # response parsing + StreamingFrameParser
|
|
696
777
|
│ ├── research.js # deep research payload extractors
|
|
697
|
-
│ ├── rotate.js # cookie rotation
|
|
778
|
+
│ ├── rotate.js # cookie rotation
|
|
698
779
|
│ └── upload.js # file upload helpers
|
|
699
780
|
└── components/
|
|
700
781
|
├── chatMixin.js # chat history methods
|
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,6 +549,7 @@ class GeminiClient {
|
|
|
340
549
|
chat = null,
|
|
341
550
|
temporary = false,
|
|
342
551
|
deep_research = false,
|
|
552
|
+
extended_thinking = false,
|
|
343
553
|
} = {}) {
|
|
344
554
|
if (!this._running) {
|
|
345
555
|
await this.init({
|
|
@@ -369,7 +579,7 @@ class GeminiClient {
|
|
|
369
579
|
const ss = { last_texts: {}, last_thoughts: {}, last_progress_time: Date.now(), is_thinking: false, is_queueing: false };
|
|
370
580
|
let output = null;
|
|
371
581
|
for await (const out of this._generate({
|
|
372
|
-
prompt, fileData, model, gem, chat, temporary, ss, deep_research,
|
|
582
|
+
prompt, fileData, model, gem, chat, temporary, ss, deep_research, extended_thinking,
|
|
373
583
|
})) output = out;
|
|
374
584
|
if (!output) throw new GeminiError('Failed to generate contents. No output data found in response.');
|
|
375
585
|
if (chat instanceof ChatSession) { output.metadata = chat.metadata; chat.lastOutput = output; }
|
|
@@ -386,6 +596,7 @@ class GeminiClient {
|
|
|
386
596
|
chat = null,
|
|
387
597
|
temporary = false,
|
|
388
598
|
deep_research = false,
|
|
599
|
+
extended_thinking = false,
|
|
389
600
|
} = {}) {
|
|
390
601
|
if (!this._running) {
|
|
391
602
|
await this.init({
|
|
@@ -414,7 +625,7 @@ class GeminiClient {
|
|
|
414
625
|
const ss = { last_texts: {}, last_thoughts: {} };
|
|
415
626
|
let output = null;
|
|
416
627
|
for await (const out of this._generate({
|
|
417
|
-
prompt, fileData, model, gem, chat, temporary, ss, deep_research,
|
|
628
|
+
prompt, fileData, model, gem, chat, temporary, ss, deep_research, extended_thinking,
|
|
418
629
|
})) {
|
|
419
630
|
output = out;
|
|
420
631
|
yield out;
|
|
@@ -431,6 +642,7 @@ class GeminiClient {
|
|
|
431
642
|
temporary = false,
|
|
432
643
|
ss = null,
|
|
433
644
|
deep_research = false,
|
|
645
|
+
extended_thinking = false,
|
|
434
646
|
}, retries = 5) {
|
|
435
647
|
if (!prompt) throw new Error('Prompt cannot be empty.');
|
|
436
648
|
|
|
@@ -438,7 +650,7 @@ class GeminiClient {
|
|
|
438
650
|
|
|
439
651
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
440
652
|
try {
|
|
441
|
-
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;
|
|
442
654
|
return;
|
|
443
655
|
} catch (e) {
|
|
444
656
|
if (
|
|
@@ -451,7 +663,7 @@ class GeminiClient {
|
|
|
451
663
|
}
|
|
452
664
|
}
|
|
453
665
|
|
|
454
|
-
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 }) {
|
|
455
667
|
const _reqid = this._reqid;
|
|
456
668
|
this._reqid += 100000;
|
|
457
669
|
const gemId = gem instanceof Gem ? gem.id : gem;
|
|
@@ -463,7 +675,7 @@ class GeminiClient {
|
|
|
463
675
|
rcid: chat.rcid,
|
|
464
676
|
} : null;
|
|
465
677
|
|
|
466
|
-
const inner = new Array(
|
|
678
|
+
const inner = new Array(81).fill(null);
|
|
467
679
|
inner[0] = [prompt, 0, null, fileData, null, null, 0];
|
|
468
680
|
inner[1] = [this.language || 'en'];
|
|
469
681
|
inner[2] = chat instanceof ChatSession ? chat.metadata : [...DEFAULT_METADATA];
|
|
@@ -489,11 +701,24 @@ class GeminiClient {
|
|
|
489
701
|
inner[55] = [[1]];
|
|
490
702
|
}
|
|
491
703
|
inner[61] = [];
|
|
492
|
-
inner[68] =
|
|
704
|
+
inner[68] = 1;
|
|
705
|
+
inner[80] = extended_thinking ? 2 : 1;
|
|
493
706
|
|
|
494
707
|
const uid = uuidv4().toUpperCase();
|
|
495
708
|
inner[59] = uid;
|
|
496
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
|
+
|
|
497
722
|
const params = new URLSearchParams({
|
|
498
723
|
hl: this.language || 'en',
|
|
499
724
|
_reqid: String(_reqid),
|
|
@@ -516,7 +741,7 @@ class GeminiClient {
|
|
|
516
741
|
{
|
|
517
742
|
headers: {
|
|
518
743
|
...Headers.GEMINI,
|
|
519
|
-
...
|
|
744
|
+
...modelHeaders,
|
|
520
745
|
'x-goog-ext-525005358-jspb': `["${uid}",1]`,
|
|
521
746
|
...Headers.SAME_DOMAIN,
|
|
522
747
|
'Cookie': cookieStr(this.cookies),
|
|
@@ -541,7 +766,7 @@ class GeminiClient {
|
|
|
541
766
|
let isCompleted = false, isFinalChunk = false;
|
|
542
767
|
let cid = chat instanceof ChatSession ? chat.cid : '';
|
|
543
768
|
let rid = chat instanceof ChatSession ? chat.rid : '';
|
|
544
|
-
|
|
769
|
+
const frameParser = new StreamingFrameParser();
|
|
545
770
|
|
|
546
771
|
const processParts = (parts) => {
|
|
547
772
|
const outs = [];
|
|
@@ -676,10 +901,7 @@ class GeminiClient {
|
|
|
676
901
|
|
|
677
902
|
res.data.on('data', chunk => {
|
|
678
903
|
try {
|
|
679
|
-
|
|
680
|
-
if (buf.startsWith(")]}'")) buf = buf.slice(4).trimStart();
|
|
681
|
-
const [parts, rem] = parseResponseByFrame(buf);
|
|
682
|
-
buf = rem;
|
|
904
|
+
const parts = frameParser.feed(chunk.toString('utf8'));
|
|
683
905
|
const outs = processParts(parts);
|
|
684
906
|
for (const o of outs) yielded.push(o);
|
|
685
907
|
if (outs.length || isThinking || isQueueing) lastProg = Date.now();
|
|
@@ -692,10 +914,8 @@ class GeminiClient {
|
|
|
692
914
|
res.data.on('end', () => {
|
|
693
915
|
clearInterval(watchdog);
|
|
694
916
|
try {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
for (const o of processParts(p)) yielded.push(o);
|
|
698
|
-
}
|
|
917
|
+
const remaining = frameParser.flush();
|
|
918
|
+
for (const o of processParts(remaining)) yielded.push(o);
|
|
699
919
|
if (!isCompleted && !isFinalChunk) reject(new APIError('Stream interrupted or truncated.'));
|
|
700
920
|
else resolve();
|
|
701
921
|
} catch (e) { reject(e); }
|
|
@@ -923,7 +1143,7 @@ class ChatSession {
|
|
|
923
1143
|
get rcid() { return this._metadata[2]; }
|
|
924
1144
|
set rcid(v) { this._metadata[2] = v; }
|
|
925
1145
|
|
|
926
|
-
async sendMessage({ prompt, files = null, temporary = false, deep_research = false } = {}) {
|
|
1146
|
+
async sendMessage({ prompt, files = null, temporary = false, deep_research = false, extended_thinking = false } = {}) {
|
|
927
1147
|
return this.geminiclient.generateContent({
|
|
928
1148
|
prompt,
|
|
929
1149
|
files,
|
|
@@ -932,10 +1152,11 @@ class ChatSession {
|
|
|
932
1152
|
chat: this,
|
|
933
1153
|
temporary,
|
|
934
1154
|
deep_research,
|
|
1155
|
+
extended_thinking,
|
|
935
1156
|
});
|
|
936
1157
|
}
|
|
937
1158
|
|
|
938
|
-
async* sendMessageStream({ prompt, files = null, temporary = false, deep_research = false } = {}) {
|
|
1159
|
+
async* sendMessageStream({ prompt, files = null, temporary = false, deep_research = false, extended_thinking = false } = {}) {
|
|
939
1160
|
for await (const out of this.geminiclient.generateContentStream({
|
|
940
1161
|
prompt,
|
|
941
1162
|
files,
|
|
@@ -944,6 +1165,7 @@ class ChatSession {
|
|
|
944
1165
|
chat: this,
|
|
945
1166
|
temporary,
|
|
946
1167
|
deep_research,
|
|
1168
|
+
extended_thinking,
|
|
947
1169
|
})) yield out;
|
|
948
1170
|
}
|
|
949
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/constants.js
CHANGED
|
@@ -9,11 +9,11 @@ const ARTIFACTS_RE = /http:\/\/googleusercontent\.com\/\w+\/\d+\n*/g;
|
|
|
9
9
|
const DEFAULT_METADATA = ['', '', '', null, null, null, null, null, null, ''];
|
|
10
10
|
const MODEL_HEADER_KEY = 'x-goog-ext-525001261-jspb';
|
|
11
11
|
|
|
12
|
-
function buildModelHeader(modelId, capacityTail) {
|
|
12
|
+
function buildModelHeader(modelId, capacityTail, modelNumber = 1) {
|
|
13
13
|
return {
|
|
14
|
-
[MODEL_HEADER_KEY]: `[1,null,null,null,"${modelId}",null,null,0,[4],null,null,${capacityTail}]`,
|
|
14
|
+
[MODEL_HEADER_KEY]: `[1,null,null,null,"${modelId}",null,null,0,[4,5,6,8],null,null,${capacityTail},null,null,${modelNumber}]`,
|
|
15
15
|
'x-goog-ext-73010989-jspb': '[0]',
|
|
16
|
-
'x-goog-ext-73010990-jspb': '[0]',
|
|
16
|
+
'x-goog-ext-73010990-jspb': '[0,0,0]',
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -29,22 +29,56 @@ const Endpoint = {
|
|
|
29
29
|
const GRPC = {
|
|
30
30
|
LIST_CHATS: 'MaZiqc',
|
|
31
31
|
READ_CHAT: 'hNvQHb',
|
|
32
|
+
GET_CONVERSATION_TURN: 'EqPOKe',
|
|
32
33
|
DELETE_CHAT_1: 'GzXR5e',
|
|
33
34
|
DELETE_CHAT_2: 'qWymEb',
|
|
35
|
+
UPDATE_CONVERSATION: 'MUAZcd',
|
|
36
|
+
MARK_LAST_CONVERSATION_TURN: 'kOWVAe',
|
|
37
|
+
GENERATE_HEADLINE: 'ukz1Fe',
|
|
38
|
+
|
|
34
39
|
LIST_GEMS: 'CNgdBe',
|
|
35
40
|
CREATE_GEM: 'oMH3Zd',
|
|
41
|
+
GET_GEM: 'HcT8bb',
|
|
36
42
|
UPDATE_GEM: 'kHv0Vd',
|
|
37
43
|
DELETE_GEM: 'UXcSJb',
|
|
44
|
+
DELETE_GEM_AND_CONVERSATIONS: 'Nwkn9',
|
|
45
|
+
|
|
46
|
+
CREATE_TASK: 'Jba3ib',
|
|
47
|
+
GET_TASK: 'kwDCne',
|
|
48
|
+
GET_ALL_TASKS: 'XPSWpd',
|
|
49
|
+
GET_TASKS_IN_CONVERSATION: 'qWymEb',
|
|
50
|
+
GET_CANDIDATES: 'PCck7e',
|
|
51
|
+
LIST_DISCOVERY_CARDS: 'ku4Jyf',
|
|
52
|
+
GET_DISCOVERY_CARD: 'oApPWc',
|
|
53
|
+
LIST_DISCOVERY_BANNERS: 'Te6DCf',
|
|
54
|
+
|
|
38
55
|
DEEP_RESEARCH_STATUS: 'kwDCne',
|
|
39
56
|
DEEP_RESEARCH_PREFS: 'L5adhe',
|
|
40
57
|
DEEP_RESEARCH_BOOTSTRAP: 'ku4Jyf',
|
|
41
58
|
DEEP_RESEARCH_MODEL_STATE: 'qpEbW',
|
|
42
59
|
DEEP_RESEARCH_CAPS: 'aPya6c',
|
|
43
60
|
DEEP_RESEARCH_ACK: 'PCck7e',
|
|
61
|
+
|
|
62
|
+
LIST_GEMINI_APP_ARTIFACTS: 'jGArJ',
|
|
63
|
+
DELETE_GEMINI_APP_ARTIFACTS: 'PGX16d',
|
|
64
|
+
|
|
65
|
+
LIST_MEMORIES: 'ZKcapf',
|
|
66
|
+
CREATE_MEMORY: 'xVRQX',
|
|
67
|
+
UPDATE_MEMORY: 'gSnMcd',
|
|
68
|
+
DELETE_MEMORY: 'Ok9j9b',
|
|
69
|
+
DELETE_ALL_MEMORIES: 'YgU2Cc',
|
|
70
|
+
|
|
44
71
|
GET_USER_STATUS: 'otAQ7b',
|
|
45
72
|
LIST_MODELS: 'otAQ7b',
|
|
73
|
+
CHECK_GEMINI_QUOTA: 'qpEbW',
|
|
74
|
+
CHECK_QUOTA: 'aPya6c',
|
|
46
75
|
GET_FULL_SIZE_IMAGE: 'c8o8Fe',
|
|
76
|
+
GET_ABUSE_STATUS: 'GPRiHf',
|
|
77
|
+
UPDATE_USER_PREFERENCES: 'L5adhe',
|
|
78
|
+
READ_USER_PREFERENCES: 'ESY5D',
|
|
47
79
|
BARD_SETTINGS: 'ESY5D',
|
|
80
|
+
CONTINUE_SHARED_CONVERSATION: 'ra9Swb',
|
|
81
|
+
GET_USAGE_INFO: 'jSf9Qc',
|
|
48
82
|
};
|
|
49
83
|
|
|
50
84
|
const Headers = {
|
|
@@ -66,67 +100,69 @@ const Headers = {
|
|
|
66
100
|
},
|
|
67
101
|
UPLOAD: { 'X-Tenant-Id': 'bard-storage' },
|
|
68
102
|
BATCH_EXEC: {
|
|
69
|
-
'x-goog-ext-525001261-jspb': '[1,null,null,null,null,null,null,null,[4]]',
|
|
103
|
+
'x-goog-ext-525001261-jspb': '[1,null,null,null,null,null,null,null,[4,5,6,8],null,null,null,null,null,null,null]',
|
|
70
104
|
'x-goog-ext-73010989-jspb': '[0]',
|
|
71
105
|
},
|
|
72
106
|
};
|
|
73
107
|
|
|
74
108
|
const _MODEL_KEYS = [
|
|
75
|
-
'UNSPECIFIED',
|
|
76
|
-
'
|
|
77
|
-
'
|
|
109
|
+
'UNSPECIFIED',
|
|
110
|
+
'BASIC_PRO', 'BASIC_FLASH', 'BASIC_LITE',
|
|
111
|
+
'PLUS_PRO', 'PLUS_FLASH', 'PLUS_LITE',
|
|
112
|
+
'ADVANCED_PRO', 'ADVANCED_FLASH', 'ADVANCED_LITE',
|
|
78
113
|
];
|
|
79
114
|
|
|
80
115
|
const Model = {
|
|
81
116
|
UNSPECIFIED: { model_name: 'unspecified', model_header: {}, advanced_only: false },
|
|
82
117
|
BASIC_PRO: {
|
|
83
118
|
model_name: 'gemini-3-pro',
|
|
84
|
-
model_header: buildModelHeader('9d8ca3786ebdfbea', 1),
|
|
119
|
+
model_header: buildModelHeader('9d8ca3786ebdfbea', 1, 3),
|
|
85
120
|
advanced_only: false,
|
|
86
121
|
},
|
|
87
122
|
BASIC_FLASH: {
|
|
88
123
|
model_name: 'gemini-3-flash',
|
|
89
|
-
model_header: buildModelHeader('fbb127bbb056c959', 1),
|
|
124
|
+
model_header: buildModelHeader('fbb127bbb056c959', 1, 1),
|
|
90
125
|
advanced_only: false,
|
|
91
126
|
},
|
|
92
|
-
|
|
93
|
-
model_name: 'gemini-3-
|
|
94
|
-
model_header: buildModelHeader('
|
|
127
|
+
BASIC_LITE: {
|
|
128
|
+
model_name: 'gemini-3-lite',
|
|
129
|
+
model_header: buildModelHeader('cf41b0e0dd7d53e5', 1, 6),
|
|
95
130
|
advanced_only: false,
|
|
96
131
|
},
|
|
97
132
|
PLUS_PRO: {
|
|
98
133
|
model_name: 'gemini-3-pro-plus',
|
|
99
|
-
model_header: buildModelHeader('e6fa609c3fa255c0', 4),
|
|
134
|
+
model_header: buildModelHeader('e6fa609c3fa255c0', 4, 3),
|
|
100
135
|
advanced_only: true,
|
|
101
136
|
},
|
|
102
137
|
PLUS_FLASH: {
|
|
103
138
|
model_name: 'gemini-3-flash-plus',
|
|
104
|
-
model_header: buildModelHeader('56fdd199312815e2', 4),
|
|
139
|
+
model_header: buildModelHeader('56fdd199312815e2', 4, 1),
|
|
105
140
|
advanced_only: true,
|
|
106
141
|
},
|
|
107
|
-
|
|
108
|
-
model_name: 'gemini-3-
|
|
109
|
-
model_header: buildModelHeader('
|
|
142
|
+
PLUS_LITE: {
|
|
143
|
+
model_name: 'gemini-3-lite-plus',
|
|
144
|
+
model_header: buildModelHeader('8c46e95b1a07cecc', 4, 6),
|
|
110
145
|
advanced_only: true,
|
|
111
146
|
},
|
|
112
147
|
ADVANCED_PRO: {
|
|
113
148
|
model_name: 'gemini-3-pro-advanced',
|
|
114
|
-
model_header: buildModelHeader('e6fa609c3fa255c0', 2),
|
|
149
|
+
model_header: buildModelHeader('e6fa609c3fa255c0', 2, 3),
|
|
115
150
|
advanced_only: true,
|
|
116
151
|
},
|
|
117
152
|
ADVANCED_FLASH: {
|
|
118
153
|
model_name: 'gemini-3-flash-advanced',
|
|
119
|
-
model_header: buildModelHeader('56fdd199312815e2', 2),
|
|
154
|
+
model_header: buildModelHeader('56fdd199312815e2', 2, 1),
|
|
120
155
|
advanced_only: true,
|
|
121
156
|
},
|
|
122
|
-
|
|
123
|
-
model_name: 'gemini-3-
|
|
124
|
-
model_header: buildModelHeader('
|
|
157
|
+
ADVANCED_LITE: {
|
|
158
|
+
model_name: 'gemini-3-lite-advanced',
|
|
159
|
+
model_header: buildModelHeader('8c46e95b1a07cecc', 2, 6),
|
|
125
160
|
advanced_only: true,
|
|
126
161
|
},
|
|
127
162
|
fromName(name) {
|
|
163
|
+
const lower = name.toLowerCase();
|
|
128
164
|
for (const k of _MODEL_KEYS) {
|
|
129
|
-
if (Model[k].model_name ===
|
|
165
|
+
if (Model[k].model_name === lower) return Model[k];
|
|
130
166
|
}
|
|
131
167
|
const names = _MODEL_KEYS.map(k => Model[k].model_name).join(', ');
|
|
132
168
|
throw new Error(`Unknown model name: ${name}. Available: ${names}`);
|
package/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface ModelDict {
|
|
|
18
18
|
|
|
19
19
|
export type ModelInput = string | ModelDef | ModelDict | AvailableModel | null;
|
|
20
20
|
|
|
21
|
-
export declare function buildModelHeader(modelId: string, capacityTail: string | number): ModelHeader;
|
|
21
|
+
export declare function buildModelHeader(modelId: string, capacityTail: string | number, modelNumber?: number): ModelHeader;
|
|
22
22
|
|
|
23
23
|
export declare const MODEL_HEADER_KEY: string;
|
|
24
24
|
export declare const STREAMING_FLAG_INDEX: number;
|
|
@@ -125,6 +125,7 @@ export declare class AvailableModel {
|
|
|
125
125
|
description: string;
|
|
126
126
|
capacity: number;
|
|
127
127
|
capacity_field: number;
|
|
128
|
+
model_number: number;
|
|
128
129
|
is_available: boolean;
|
|
129
130
|
constructor(opts: {
|
|
130
131
|
model_id: string;
|
|
@@ -133,6 +134,7 @@ export declare class AvailableModel {
|
|
|
133
134
|
description: string;
|
|
134
135
|
capacity: number;
|
|
135
136
|
capacity_field?: number;
|
|
137
|
+
model_number?: number;
|
|
136
138
|
is_available?: boolean;
|
|
137
139
|
});
|
|
138
140
|
get model_header(): ModelHeader;
|
|
@@ -141,6 +143,7 @@ export declare class AvailableModel {
|
|
|
141
143
|
repr(): string;
|
|
142
144
|
static computeCapacity(tierFlags: number[], capabilityFlags: number[]): [number, number];
|
|
143
145
|
static buildModelIdNameMapping(): Record<string, string>;
|
|
146
|
+
static buildModelIdNumberMapping(): Record<string, number>;
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
export interface ImageSaveOptions {
|
|
@@ -417,6 +420,7 @@ export interface GenerateOptions {
|
|
|
417
420
|
chat?: ChatSession | null;
|
|
418
421
|
temporary?: boolean;
|
|
419
422
|
deep_research?: boolean;
|
|
423
|
+
extended_thinking?: boolean;
|
|
420
424
|
}
|
|
421
425
|
|
|
422
426
|
export interface StartChatOptions {
|
|
@@ -461,6 +465,9 @@ export declare class GeminiClient {
|
|
|
461
465
|
refreshInterval: number;
|
|
462
466
|
verbose: boolean;
|
|
463
467
|
watchdogTimeout: number;
|
|
468
|
+
readonly quotas: Record<string, Record<string, unknown>>;
|
|
469
|
+
readonly usageInfo: Record<string, unknown>;
|
|
470
|
+
readonly abuseStatus: { is_clean: boolean; status_code: number | null; signal: unknown } | null;
|
|
464
471
|
|
|
465
472
|
constructor(opts?: GeminiClientOptions);
|
|
466
473
|
|
|
@@ -508,6 +515,7 @@ export interface SendMessageOptions {
|
|
|
508
515
|
files?: (string | Buffer)[] | null;
|
|
509
516
|
temporary?: boolean;
|
|
510
517
|
deep_research?: boolean;
|
|
518
|
+
extended_thinking?: boolean;
|
|
511
519
|
}
|
|
512
520
|
|
|
513
521
|
export declare class ChatSession {
|
package/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
AuthError, APIError, ImageGenerationError, GeminiError, TimeoutError,
|
|
12
12
|
UsageLimitExceeded, ModelInvalid, TemporarilyBlocked,
|
|
13
13
|
} = require('./exceptions');
|
|
14
|
+
const { StreamingFrameParser } = require('./utils/parsing');
|
|
14
15
|
const {
|
|
15
16
|
Candidate, ChatHistory, ChatTurn, ChatInfo, Gem, GemJar, RPCData,
|
|
16
17
|
Image, WebImage, GeneratedImage, ModelOutput,
|
|
@@ -61,4 +62,5 @@ module.exports = {
|
|
|
61
62
|
GeneratedVideo,
|
|
62
63
|
GeneratedMedia,
|
|
63
64
|
AvailableModel,
|
|
65
|
+
StreamingFrameParser,
|
|
64
66
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gemini-reverse",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Unofficial Node.js client for gemini.google.com — inspired by Gemini-API (Python). Supports streaming, chat sessions, gems, file uploads, and TypeScript.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
package/types/availablemodel.js
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
const { buildModelHeader, MODEL_HEADER_KEY, Model } = require('../constants');
|
|
4
4
|
|
|
5
5
|
class AvailableModel {
|
|
6
|
-
constructor({ model_id, model_name, display_name, description, capacity, capacity_field = 12, is_available = true } = {}) {
|
|
6
|
+
constructor({ model_id, model_name, display_name, description, capacity, capacity_field = 12, model_number = 1, is_available = true } = {}) {
|
|
7
7
|
this.model_id = model_id;
|
|
8
8
|
this.model_name = model_name;
|
|
9
9
|
this.display_name = display_name;
|
|
10
10
|
this.description = description;
|
|
11
11
|
this.capacity = capacity;
|
|
12
12
|
this.capacity_field = capacity_field;
|
|
13
|
+
this.model_number = model_number;
|
|
13
14
|
this.is_available = is_available;
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -20,7 +21,7 @@ class AvailableModel {
|
|
|
20
21
|
} else {
|
|
21
22
|
tail = String(this.capacity);
|
|
22
23
|
}
|
|
23
|
-
return buildModelHeader(this.model_id, tail);
|
|
24
|
+
return buildModelHeader(this.model_id, tail, this.model_number);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
get advanced_only() {
|
|
@@ -47,9 +48,9 @@ class AvailableModel {
|
|
|
47
48
|
static buildModelIdNameMapping() {
|
|
48
49
|
const result = {};
|
|
49
50
|
const keys = [
|
|
50
|
-
'BASIC_PRO', 'BASIC_FLASH', '
|
|
51
|
-
'PLUS_PRO', 'PLUS_FLASH', '
|
|
52
|
-
'ADVANCED_PRO', 'ADVANCED_FLASH', '
|
|
51
|
+
'BASIC_PRO', 'BASIC_FLASH', 'BASIC_LITE',
|
|
52
|
+
'PLUS_PRO', 'PLUS_FLASH', 'PLUS_LITE',
|
|
53
|
+
'ADVANCED_PRO', 'ADVANCED_FLASH', 'ADVANCED_LITE',
|
|
53
54
|
];
|
|
54
55
|
for (const key of keys) {
|
|
55
56
|
const member = Model[key];
|
|
@@ -71,6 +72,32 @@ class AvailableModel {
|
|
|
71
72
|
}
|
|
72
73
|
return result;
|
|
73
74
|
}
|
|
75
|
+
|
|
76
|
+
static buildModelIdNumberMapping() {
|
|
77
|
+
const result = {};
|
|
78
|
+
const keys = [
|
|
79
|
+
'BASIC_PRO', 'BASIC_FLASH', 'BASIC_LITE',
|
|
80
|
+
'PLUS_PRO', 'PLUS_FLASH', 'PLUS_LITE',
|
|
81
|
+
'ADVANCED_PRO', 'ADVANCED_FLASH', 'ADVANCED_LITE',
|
|
82
|
+
];
|
|
83
|
+
for (const key of keys) {
|
|
84
|
+
const member = Model[key];
|
|
85
|
+
if (!member) continue;
|
|
86
|
+
const headerValue = member.model_header[MODEL_HEADER_KEY];
|
|
87
|
+
if (!headerValue) continue;
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(headerValue);
|
|
90
|
+
const modelId = parsed && parsed[4];
|
|
91
|
+
const modelNumber = parsed && parsed[parsed.length - 1];
|
|
92
|
+
if (modelId && typeof modelNumber === 'number' && !(modelId in result)) {
|
|
93
|
+
result[modelId] = modelNumber;
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
74
101
|
}
|
|
75
102
|
|
|
76
103
|
module.exports = { AvailableModel };
|
package/utils/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { getAccessToken, sendInitRequest, cookieStr, parseCookies, parseProxy, cacheDir } = require('./accessToken');
|
|
4
|
-
const { getDeltaByFpLen, getCleanText, getNestedValue, parseResponseByFrame, extractJsonFromResponse } = require('./parsing');
|
|
4
|
+
const { getDeltaByFpLen, getCleanText, getNestedValue, parseResponseByFrame, extractJsonFromResponse, StreamingFrameParser } = require('./parsing');
|
|
5
5
|
const { rotate1psidts } = require('./rotate');
|
|
6
6
|
const { uploadFile, parseFileName, generateRandomName } = require('./upload');
|
|
7
7
|
const {
|
|
@@ -28,6 +28,7 @@ module.exports = {
|
|
|
28
28
|
getNestedValue,
|
|
29
29
|
parseResponseByFrame,
|
|
30
30
|
extractJsonFromResponse,
|
|
31
|
+
StreamingFrameParser,
|
|
31
32
|
rotate1psidts,
|
|
32
33
|
uploadFile,
|
|
33
34
|
parseFileName,
|
package/utils/parsing.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const FLICKER_ESC_RE = /\\+[`*_~].*$/;
|
|
4
|
+
const LENGTH_MARKER_RE = /^(\d+)\n/;
|
|
4
5
|
|
|
5
6
|
function getCleanText(s) {
|
|
6
7
|
if (!s) return '';
|
|
@@ -90,45 +91,121 @@ function getNestedValue(data, path, defaultVal = null) {
|
|
|
90
91
|
return cur != null ? cur : defaultVal;
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
94
|
+
class StreamingFrameParser {
|
|
95
|
+
constructor() {
|
|
96
|
+
this.buffer = '';
|
|
97
|
+
this.expectedUnits = null;
|
|
98
|
+
this.payloadStart = 0;
|
|
99
|
+
this.scannedChars = 0;
|
|
100
|
+
this.scannedUnits = 0;
|
|
101
|
+
this.prefixChecked = false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
reset() {
|
|
105
|
+
this.buffer = '';
|
|
106
|
+
this._resetFrameState();
|
|
107
|
+
this.prefixChecked = false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
feed(content) {
|
|
111
|
+
if (typeof content !== 'string') throw new TypeError(`Expected string, got ${typeof content}`);
|
|
112
|
+
if (content) this.buffer += content;
|
|
113
|
+
this._stripPrefixOnce();
|
|
114
|
+
|
|
115
|
+
const parsed = [];
|
|
116
|
+
while (true) {
|
|
117
|
+
if (this.expectedUnits === null && !this._readLengthMarker()) break;
|
|
118
|
+
if (this.expectedUnits === null) break;
|
|
119
|
+
|
|
120
|
+
this._scanAvailablePayload();
|
|
121
|
+
if (this.scannedUnits < this.expectedUnits) break;
|
|
122
|
+
|
|
123
|
+
const endPos = this.payloadStart + this.scannedChars;
|
|
124
|
+
const chunk = this.buffer.slice(this.payloadStart, endPos);
|
|
125
|
+
this.buffer = this.buffer.slice(endPos);
|
|
126
|
+
this._resetFrameState();
|
|
127
|
+
|
|
128
|
+
if (!chunk.trim()) continue;
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const p = JSON.parse(chunk);
|
|
132
|
+
if (Array.isArray(p)) parsed.push(...p);
|
|
133
|
+
else parsed.push(p);
|
|
134
|
+
} catch {}
|
|
135
|
+
}
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
flush() {
|
|
140
|
+
return this.feed('');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
_resetFrameState() {
|
|
144
|
+
this.expectedUnits = null;
|
|
145
|
+
this.payloadStart = 0;
|
|
146
|
+
this.scannedChars = 0;
|
|
147
|
+
this.scannedUnits = 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
_stripPrefixOnce() {
|
|
151
|
+
if (this.prefixChecked) return;
|
|
152
|
+
const prefix = ")]}'";
|
|
153
|
+
if (this.buffer.length < prefix.length && prefix.startsWith(this.buffer)) return;
|
|
154
|
+
if (this.buffer.startsWith(prefix)) {
|
|
155
|
+
this.buffer = this.buffer.slice(prefix.length).trimStart();
|
|
156
|
+
}
|
|
157
|
+
this.prefixChecked = true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
_readLengthMarker() {
|
|
161
|
+
let pos = 0;
|
|
162
|
+
while (pos < this.buffer.length && /\s/.test(this.buffer[pos])) pos++;
|
|
163
|
+
if (pos) {
|
|
164
|
+
this.buffer = this.buffer.slice(pos);
|
|
165
|
+
}
|
|
166
|
+
if (!this.buffer.length) return false;
|
|
167
|
+
|
|
168
|
+
const m = LENGTH_MARKER_RE.exec(this.buffer);
|
|
169
|
+
if (!m) return false;
|
|
170
|
+
|
|
171
|
+
const lenStr = m[1];
|
|
172
|
+
this.expectedUnits = parseInt(lenStr, 10);
|
|
173
|
+
this.payloadStart = lenStr.length;
|
|
174
|
+
this.scannedChars = 0;
|
|
175
|
+
this.scannedUnits = 0;
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_scanAvailablePayload() {
|
|
180
|
+
if (this.expectedUnits === null) return;
|
|
181
|
+
let idx = this.payloadStart + this.scannedChars;
|
|
182
|
+
const limit = this.buffer.length;
|
|
183
|
+
|
|
184
|
+
while (this.scannedUnits < this.expectedUnits && idx < limit) {
|
|
185
|
+
const cp = this.buffer.codePointAt(idx);
|
|
106
186
|
const u = cp > 0xFFFF ? 2 : 1;
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
187
|
+
if (this.scannedUnits + u > this.expectedUnits) break;
|
|
188
|
+
this.scannedUnits += u;
|
|
189
|
+
this.scannedChars += cp > 0xFFFF ? 2 : 1;
|
|
110
190
|
idx += cp > 0xFFFF ? 2 : 1;
|
|
111
191
|
}
|
|
112
|
-
if (units < len) break;
|
|
113
|
-
const end = start + chars;
|
|
114
|
-
const chunk = content.slice(start, end).trim();
|
|
115
|
-
pos = end;
|
|
116
|
-
if (!chunk) continue;
|
|
117
|
-
try {
|
|
118
|
-
const parsed = JSON.parse(chunk);
|
|
119
|
-
if (Array.isArray(parsed)) frames.push(...parsed);
|
|
120
|
-
else frames.push(parsed);
|
|
121
|
-
} catch {}
|
|
122
192
|
}
|
|
123
|
-
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function parseResponseByFrame(content) {
|
|
196
|
+
const parser = new StreamingFrameParser();
|
|
197
|
+
const frames = parser.feed(content);
|
|
198
|
+
frames.push(...parser.flush());
|
|
199
|
+
return [frames, parser.buffer];
|
|
124
200
|
}
|
|
125
201
|
|
|
126
202
|
function extractJsonFromResponse(text) {
|
|
127
203
|
if (typeof text !== 'string') throw new TypeError(`Expected string, got ${typeof text}`);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (
|
|
204
|
+
const parser = new StreamingFrameParser();
|
|
205
|
+
const result = parser.feed(text);
|
|
206
|
+
result.push(...parser.flush());
|
|
207
|
+
if (result.length) return result;
|
|
208
|
+
const c = text.startsWith(")]}'") ? text.slice(4).trimStart() : text.trimStart();
|
|
132
209
|
try {
|
|
133
210
|
const p = JSON.parse(c.trim());
|
|
134
211
|
return Array.isArray(p) ? p : [p];
|
|
@@ -145,4 +222,4 @@ function extractJsonFromResponse(text) {
|
|
|
145
222
|
throw new Error('Could not find valid JSON in response.');
|
|
146
223
|
}
|
|
147
224
|
|
|
148
|
-
module.exports = { getCleanText, getDeltaByFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse };
|
|
225
|
+
module.exports = { getCleanText, getDeltaByFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse, StreamingFrameParser };
|