gemini-reverse 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,25 +1,59 @@
1
- ![Banner](https://nc-cdn.oss-accelerate.aliyuncs.com/nx/a772a980daeb.png)
1
+ ![Banner](https://napkinsdev.s3.us-east-1.amazonaws.com/next-s3-uploads/2a5843b5-3f5e-4ccd-bd61-f1ba6d6ae267/fb866bbfc5b3.png)
2
2
 
3
3
  # Gemini-Reverse
4
4
 
5
- An unofficial Node.js client for [gemini.google.com](https://gemini.google.com), inspired by [Gemini-API](https://github.com/HanaokaYuzu/Gemini-API) — a Python reverse engineering project by [@HanaokaYuzu](https://github.com/HanaokaYuzu).
6
-
7
- This package ports the core concepts and functionality of the original Python library to Node.js, with full CommonJS and TypeScript support.
8
-
9
- ---
5
+ An unofficial Node.js client for [Google Gemini](https://gemini.google.com), inspired by [Gemini-API](https://github.com/HanaokaYuzu/Gemini-API) — a Python reverse engineering project by [@HanaokaYuzu](https://github.com/HanaokaYuzu).
10
6
 
11
7
  ## Features
12
8
 
13
- - Send messages and receive responses from Gemini
14
- - Streaming support with text deltas
15
- - Multi-turn chat sessions with conversation history
16
- - File and image upload support
17
- - Gem (system prompt) management create, update, delete, fetch
18
- - Auto cookie refresh to keep sessions alive
19
- - TypeScript type declarations included
20
- - Proxy support
21
-
22
- ---
9
+ - **Persistent Cookies** Automatically refreshes cookies in the background. Optimized for always-on services.
10
+ - **Image Generation** — Natively supports generating and editing images with natural language.
11
+ - **Video & Audio Generation** Supports generating videos and audio/music content natively.
12
+ - **Deep Research** — Full deep research workflow with plan creation, status polling, and result retrieval.
13
+ - **System Prompt** Supports customizing the model's system prompt with [Gemini Gems](https://gemini.google.com/gems/view).
14
+ - **Extension Support** Supports generating content with Gemini extensions such as YouTube and Gmail.
15
+ - **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
+ - **Dynamic Model Discovery** — Automatically discovers available models from your account at initialization.
18
+ - **TypeScript Support** — Full TypeScript type declarations included out of the box.
19
+
20
+ ## Table of Contents
21
+
22
+ - [Features](#features)
23
+ - [Table of Contents](#table-of-contents)
24
+ - [Installation](#installation)
25
+ - [Authentication](#authentication)
26
+ - [Usage](#usage)
27
+ - [Initialization](#initialization)
28
+ - [Generate Content](#generate-content)
29
+ - [Generate Content with Files](#generate-content-with-files)
30
+ - [Conversations Across Multiple Turns](#conversations-across-multiple-turns)
31
+ - [Continue Previous Conversations](#continue-previous-conversations)
32
+ - [Read Conversation History](#read-conversation-history)
33
+ - [List Recent Chats](#list-recent-chats)
34
+ - [Delete a Conversation](#delete-a-conversation)
35
+ - [Temporary Mode](#temporary-mode)
36
+ - [Streaming Mode](#streaming-mode)
37
+ - [Select Language Model](#select-language-model)
38
+ - [List Available Models](#list-available-models)
39
+ - [Apply System Prompt with Gemini Gems](#apply-system-prompt-with-gemini-gems)
40
+ - [Manage Custom Gems](#manage-custom-gems)
41
+ - [Create a Custom Gem](#create-a-custom-gem)
42
+ - [Update an Existing Gem](#update-an-existing-gem)
43
+ - [Delete a Custom Gem](#delete-a-custom-gem)
44
+ - [Retrieve Model's Thought Process](#retrieve-models-thought-process)
45
+ - [Retrieve Images in Response](#retrieve-images-in-response)
46
+ - [Generate and Edit Images](#generate-and-edit-images)
47
+ - [Retrieve Videos and Audio](#retrieve-videos-and-audio)
48
+ - [Generate Content with Gemini Extensions](#generate-content-with-gemini-extensions)
49
+ - [Check and Switch to Other Reply Candidates](#check-and-switch-to-other-reply-candidates)
50
+ - [Deep Research](#deep-research)
51
+ - [Account Status](#account-status)
52
+ - [Error Handling](#error-handling)
53
+ - [Cookie Persistence](#cookie-persistence)
54
+ - [TypeScript](#typescript)
55
+ - [Project Structure](#project-structure)
56
+ - [References](#references)
23
57
 
24
58
  ## Installation
25
59
 
@@ -27,79 +61,67 @@ This package ports the core concepts and functionality of the original Python li
27
61
  npm install gemini-reverse
28
62
  ```
29
63
 
30
- ---
31
-
32
64
  ## Authentication
33
65
 
34
- This package uses browser cookies to authenticate with Gemini. You need to obtain your `__Secure-1PSID` cookie from your browser after logging in to [gemini.google.com](https://gemini.google.com).
66
+ - Go to [gemini.google.com](https://gemini.google.com) and log in with your Google account
67
+ - Press F12 to open DevTools, go to the `Application` tab → `Cookies` → `https://gemini.google.com`
68
+ - Copy the value of `__Secure-1PSID` (and optionally `__Secure-1PSIDTS`)
35
69
 
36
- **Steps:**
37
- 1. Open [gemini.google.com](https://gemini.google.com) in your browser and log in
38
- 2. Open DevTools → Application → Cookies → `https://gemini.google.com`
39
- 3. Copy the value of `__Secure-1PSID` (and optionally `__Secure-1PSIDTS`)
70
+ > `__Secure-1PSIDTS` is optional — the client will attempt to refresh and cache it automatically after the first successful initialization.
40
71
 
41
- > `__Secure-1PSIDTS` is optional — the client will attempt to refresh and cache it automatically after the first successful init.
72
+ ## Usage
42
73
 
43
- ---
74
+ ### Initialization
44
75
 
45
- ## Quick Start
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 as long as the process is alive.
46
77
 
47
78
  ```js
48
- const { GeminiClient } = require('gemini-core');
49
-
50
- const client = new GeminiClient({
51
- secure_1psid: 'YOUR_SECURE_1PSID',
52
- });
79
+ const { GeminiClient } = require('gemini-reverse');
53
80
 
54
- await client.init();
55
-
56
- const chat = client.startChat();
57
- const response = await chat.sendMessage({ prompt: 'Hello, Gemini!' });
58
-
59
- console.log(response.text);
60
- ```
61
-
62
- ---
63
-
64
- ## Usage
65
-
66
- ### Initialize Client
67
-
68
- ```js
69
81
  const client = new GeminiClient({
70
82
  secure_1psid: 'YOUR_SECURE_1PSID',
71
83
  secure_1psidts: 'YOUR_SECURE_1PSIDTS', // optional
72
- proxy: 'http://host:port', // optional
84
+ proxy: null, // optional, e.g. 'http://host:port'
73
85
  });
74
86
 
75
87
  await client.init({
76
88
  timeout: 300000, // request timeout in ms, default 300000
77
- autoClose: false, // auto close client after inactivity
89
+ autoClose: false, // auto-close client after inactivity
78
90
  closeDelay: 300000, // inactivity delay before closing in ms
79
- autoRefresh: true, // auto refresh cookies in background
91
+ autoRefresh: true, // auto-refresh cookies in the background
80
92
  refreshInterval: 540000 // cookie refresh interval in ms
81
93
  });
82
94
  ```
83
95
 
84
- ### Generate Content (single turn)
96
+ > `autoClose` and `closeDelay` are optional arguments for automatically closing the client after a period of inactivity. In an always-on service like a chatbot, it is recommended to set `autoClose` to `true` with a reasonable `closeDelay` value for better resource management.
97
+
98
+ ### Generate Content
99
+
100
+ Ask a single-turn question by calling `generateContent`, which returns a `ModelOutput` object containing the generated text, images, thoughts, and conversation metadata.
85
101
 
86
102
  ```js
87
- const response = await client.generateContent({ prompt: 'What is the capital of France?' });
103
+ const response = await client.generateContent({ prompt: 'Hello World!' });
88
104
  console.log(response.text);
89
105
  ```
90
106
 
91
- ### Streaming
107
+ ### Generate Content with Files
108
+
109
+ Gemini supports file input, including images and documents. Pass an array of file paths or `Buffer` objects alongside your text prompt.
92
110
 
93
111
  ```js
94
- for await (const chunk of client.generateContentStream({ prompt: 'Tell me a long story.' })) {
95
- process.stdout.write(chunk.text_delta);
96
- }
112
+ const response = await client.generateContent({
113
+ prompt: 'Describe the contents of these files.',
114
+ files: ['./document.pdf', './photo.png'],
115
+ });
116
+ console.log(response.text);
97
117
  ```
98
118
 
99
- ### Chat Session
119
+ ### Conversations Across Multiple Turns
120
+
121
+ Use `startChat` to create a `ChatSession` object and send messages through it. The conversation history is handled automatically and updated after each turn.
100
122
 
101
123
  ```js
102
- const chat = client.startChat({ model: 'gemini-3.1-pro' });
124
+ const chat = client.startChat();
103
125
 
104
126
  const res1 = await chat.sendMessage({ prompt: 'My name is Alice.' });
105
127
  console.log(res1.text);
@@ -108,191 +130,457 @@ const res2 = await chat.sendMessage({ prompt: 'What is my name?' });
108
130
  console.log(res2.text); // remembers context
109
131
  ```
110
132
 
111
- ### Streaming in Chat
133
+ ### Continue Previous Conversations
134
+
135
+ Pass a previous `ChatSession`'s metadata to `startChat` to resume a conversation. You can persist the metadata to a file or database to restore it across process restarts.
112
136
 
113
137
  ```js
114
- const chat = client.startChat({ model: 'gemini-3.0-flash' });
138
+ const chat = client.startChat();
139
+ await chat.sendMessage({ prompt: 'Fine weather today.' });
115
140
 
116
- for await (const chunk of chat.sendMessageStream({ prompt: 'Explain quantum computing.' })) {
117
- process.stdout.write(chunk.text_delta);
118
- }
141
+ // Save the session metadata
142
+ const savedMetadata = chat.metadata;
143
+ const savedCid = chat.cid;
144
+
145
+ // Resume in a new session
146
+ const previousChat = client.startChat({ metadata: savedMetadata });
147
+ const response = await previousChat.sendMessage({ prompt: 'What was my previous message?' });
148
+ console.log(response.text);
119
149
  ```
120
150
 
121
- ### Temporary Chat (no history saved)
151
+ ### Read Conversation History
152
+
153
+ Fetch the full conversation history of a chat by calling `readChat` with the chat ID. It returns a `ChatHistory` object containing a list of `ChatTurn` objects ordered from newest to oldest.
122
154
 
123
155
  ```js
124
156
  const chat = client.startChat();
125
- const response = await chat.sendMessage({ prompt: 'This will not appear in history.', temporary: true });
157
+ await chat.sendMessage({ prompt: 'What is the capital of France?' });
158
+
159
+ const history = await client.readChat(chat.cid);
160
+ if (history) {
161
+ for (const turn of history.turns) {
162
+ console.log(`[${turn.role.toUpperCase()}] ${turn.text}`);
163
+ }
164
+ }
165
+ ```
166
+
167
+ You can also read the history directly from the session:
168
+
169
+ ```js
170
+ const history = await chat.readHistory(10); // fetch last 10 turns
171
+ ```
172
+
173
+ ### List Recent Chats
174
+
175
+ Use `listChats` to get a list of recent chat sessions cached at initialization.
176
+
177
+ ```js
178
+ const chats = client.listChats();
179
+ if (chats) {
180
+ for (const info of chats) {
181
+ console.log(`${info.cid}: ${info.title} (pinned: ${info.is_pinned})`);
182
+ }
183
+ }
126
184
  ```
127
185
 
128
- ### Send with Files
186
+ ### Delete a Conversation
187
+
188
+ Delete a specific chat from Gemini history on the server by calling `deleteChat` with the chat ID.
129
189
 
130
190
  ```js
131
191
  const chat = client.startChat();
132
- const response = await chat.sendMessage({
133
- prompt: 'Describe this image.',
134
- files: ['./photo.jpg'],
192
+ await chat.sendMessage({ prompt: 'This is a temporary conversation.' });
193
+
194
+ await client.deleteChat(chat.cid);
195
+ console.log(`Chat deleted: ${chat.cid}`);
196
+ ```
197
+
198
+ ### Temporary Mode
199
+
200
+ Pass `temporary: true` to `generateContent` or `sendMessage` to prevent the conversation from being saved to Gemini history.
201
+
202
+ ```js
203
+ const response = await client.generateContent({
204
+ prompt: 'Hello World!',
205
+ temporary: true,
135
206
  });
136
207
  console.log(response.text);
208
+
209
+ // Also works in chat sessions
210
+ const chat = client.startChat();
211
+ await chat.sendMessage({ prompt: 'Fine weather today.', temporary: false });
212
+ const res2 = await chat.sendMessage({ prompt: "What's my last message?", temporary: true });
213
+ console.log(res2.text);
137
214
  ```
138
215
 
139
- ### Multiple Candidates
216
+ ### Streaming Mode
217
+
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, making it easy to display incremental updates.
140
219
 
141
220
  ```js
142
- const chat = client.startChat();
143
- const response = await chat.sendMessage({ prompt: 'Give me a poem.' });
221
+ for await (const chunk of client.generateContentStream({
222
+ prompt: "What's the difference between 'await' and 'async for'?",
223
+ })) {
224
+ process.stdout.write(chunk.text_delta);
225
+ }
226
+ console.log();
227
+ ```
144
228
 
145
- // list all candidates
146
- response.candidates.forEach((c, i) => console.log(`[${i}] ${c.text}`));
229
+ Streaming also works inside a chat session:
147
230
 
148
- // choose a specific candidate to continue the conversation
149
- chat.chooseCandidate(1);
231
+ ```js
232
+ const chat = client.startChat();
233
+ for await (const chunk of chat.sendMessageStream({ prompt: 'Tell me a long story.' })) {
234
+ process.stdout.write(chunk.text_delta);
235
+ }
150
236
  ```
151
237
 
152
- ### Models
238
+ ### Select Language Model
239
+
240
+ Specify which language model to use by passing a `model` argument. Available models are discovered dynamically at init time based on your account tier.
153
241
 
154
242
  ```js
155
- // use model name string
156
- const chat = client.startChat({ model: 'gemini-3.1-pro' });
243
+ const { Model } = require('gemini-reverse');
244
+
245
+ // Using a built-in constant
246
+ const response1 = await client.generateContent({
247
+ prompt: 'What is your model version?',
248
+ model: Model.BASIC_FLASH,
249
+ });
157
250
 
158
- // or use the Model constant
159
- const { Model } = require('gemini-api');
160
- const chat = client.startChat({ model: Model.G_3_0_FLASH });
251
+ // Using a model name string
252
+ const chat = client.startChat({ model: 'gemini-3-pro' });
161
253
 
162
- // or use a custom model dict
163
- const chat = client.startChat({
254
+ // Using a custom model header dict
255
+ const chat2 = client.startChat({
164
256
  model: {
165
- model_name: 'my-model',
166
- model_header: { 'x-goog-ext-525001261-jspb': '...' },
257
+ model_name: 'custom',
258
+ model_header: {
259
+ 'x-goog-ext-525001261-jspb': '[1,null,null,null,"MODEL_ID",null,null,0,[4],null,null,2]',
260
+ },
167
261
  },
168
262
  });
169
263
  ```
170
264
 
171
- **Available models:**
265
+ **Built-in model constants:**
172
266
 
173
- | String | Constant |
174
- |---|---|
175
- | `gemini-3.1-pro` | `Model.G_3_1_PRO` |
176
- | `gemini-3.0-flash` | `Model.G_3_0_FLASH` |
177
- | `gemini-3.0-flash-thinking` | `Model.G_3_0_FLASH_THINKING` |
178
- | `unspecified` (default) | `Model.UNSPECIFIED` |
267
+ | Constant | `model_name` | Notes |
268
+ |---|---|---|
269
+ | `Model.UNSPECIFIED` | `unspecified` | Default, lets Gemini choose |
270
+ | `Model.BASIC_PRO` | `gemini-3-pro` | Free tier |
271
+ | `Model.BASIC_FLASH` | `gemini-3-flash` | Free tier, fastest |
272
+ | `Model.BASIC_THINKING` | `gemini-3-flash-thinking` | Free tier, thinking model |
273
+ | `Model.PLUS_PRO` | `gemini-3-pro-plus` | Plus tier |
274
+ | `Model.PLUS_FLASH` | `gemini-3-flash-plus` | Plus tier |
275
+ | `Model.PLUS_THINKING` | `gemini-3-flash-thinking-plus` | Plus tier |
276
+ | `Model.ADVANCED_PRO` | `gemini-3-pro-advanced` | Advanced tier |
277
+ | `Model.ADVANCED_FLASH` | `gemini-3-flash-advanced` | Advanced tier |
278
+ | `Model.ADVANCED_THINKING` | `gemini-3-flash-thinking-advanced` | Advanced tier |
179
279
 
180
- ### Images
280
+ ### List Available Models
181
281
 
182
- Responses may include web images or AI-generated images.
282
+ The client dynamically discovers which models your account can access during initialization. Use `listModels` to inspect them.
183
283
 
184
284
  ```js
185
- const response = await chat.sendMessage({ prompt: 'Send me an image of a cat.' });
285
+ await client.init();
186
286
 
187
- for (const img of response.images) {
188
- console.log(img.url, img.title, img.alt);
189
- await img.save({ path: './downloads', verbose: true });
287
+ const models = client.listModels();
288
+ if (models) {
289
+ for (const model of models) {
290
+ console.log(`${model.model_id} → ${model.model_name || model.display_name}`);
291
+ console.log(` capacity: ${model.capacity}, advanced_only: ${model.advanced_only}`);
292
+ }
190
293
  }
191
294
  ```
192
295
 
193
- ### Read Chat History
296
+ ### Apply System Prompt with Gemini Gems
297
+
298
+ System prompts can be applied to conversations via [Gemini Gems](https://gemini.google.com/gems/view). Pass the `gem` argument to `generateContent` or `startChat` — it can be a `Gem` object or a gem ID string.
194
299
 
195
300
  ```js
196
- const turns = await client.readChat('c_YOUR_CHAT_ID');
301
+ // Fetch all gems for the account
302
+ await client.fetchGems();
303
+ const gems = client.gems;
304
+
305
+ // Get a specific gem
306
+ const codingPartner = gems.get({ name: 'Coding partner' });
307
+
308
+ const response = await client.generateContent({
309
+ prompt: "What's your system prompt?",
310
+ gem: codingPartner,
311
+ });
312
+ console.log(response.text);
313
+
314
+ // Use a gem in a multi-turn chat
315
+ const chat = client.startChat({ gem: codingPartner });
316
+ const res2 = await chat.sendMessage({ prompt: 'Help me write a binary search.' });
317
+ console.log(res2.text);
318
+ ```
319
+
320
+ > There are some system predefined gems that are hidden by default. Use `fetchGems({ includeHidden: true })` to include them.
321
+
322
+ ### Manage Custom Gems
323
+
324
+ You can create, update, and delete custom gems programmatically. Predefined system gems cannot be modified.
325
+
326
+ #### Create a Custom Gem
327
+
328
+ ```js
329
+ const newGem = await client.createGem({
330
+ name: 'Python Tutor',
331
+ prompt: 'You are a helpful Python programming tutor. Always provide runnable code examples.',
332
+ description: 'A specialized gem for Python programming',
333
+ });
334
+
335
+ console.log(`Created: ${newGem.id}`);
336
+
337
+ // Use the new gem immediately
338
+ const response = await client.generateContent({
339
+ prompt: 'Explain list comprehensions.',
340
+ gem: newGem,
341
+ });
342
+ console.log(response.text);
343
+ ```
344
+
345
+ #### Update an Existing Gem
346
+
347
+ > When updating a gem, all parameters (`name`, `prompt`, `description`) must be provided even if only one is changing.
197
348
 
198
- for (const turn of turns) {
199
- console.log('User:', turn.user_prompt);
200
- console.log('Gemini:', turn.assistant_response);
349
+ ```js
350
+ await client.fetchGems();
351
+ const pythonTutor = client.gems.get({ name: 'Python Tutor' });
352
+
353
+ const updatedGem = await client.updateGem({
354
+ gem: pythonTutor,
355
+ name: 'Advanced Python Tutor',
356
+ prompt: 'You are an expert Python programming tutor. Focus on performance and best practices.',
357
+ description: 'An advanced Python programming assistant',
358
+ });
359
+
360
+ console.log(`Updated: ${updatedGem.id}`);
361
+ ```
362
+
363
+ #### Delete a Custom Gem
364
+
365
+ ```js
366
+ await client.fetchGems();
367
+ const gemToDelete = client.gems.get({ name: 'Advanced Python Tutor' });
368
+
369
+ await client.deleteGem(gemToDelete); // can also pass a gem ID string
370
+ console.log(`Deleted: ${gemToDelete.name}`);
371
+ ```
372
+
373
+ ### Retrieve Model's Thought Process
374
+
375
+ When using thinking-capable models, the model's internal reasoning is exposed via `response.thoughts`.
376
+
377
+ ```js
378
+ const response = await client.generateContent({
379
+ prompt: 'What is 17 × 23?',
380
+ model: Model.BASIC_THINKING,
381
+ });
382
+
383
+ if (response.thoughts) {
384
+ console.log('Thoughts:', response.thoughts);
201
385
  }
386
+ console.log('Answer:', response.text);
202
387
  ```
203
388
 
204
- ### Delete Chat
389
+ ### Retrieve Images in Response
390
+
391
+ Images in the response are stored as a list of `Image` objects accessible via `response.images`. Each image has a `url`, `title`, and `alt` description.
205
392
 
206
393
  ```js
207
- await client.deleteChat('c_YOUR_CHAT_ID');
394
+ const response = await client.generateContent({ prompt: 'Send me some pictures of cats.' });
395
+
396
+ for (const image of response.images) {
397
+ console.log(`${image.title}: ${image.url}`);
398
+ console.log(`Alt: ${image.alt}`);
399
+ }
208
400
  ```
209
401
 
210
- ### Gems
402
+ ### Generate and Edit Images
403
+
404
+ Ask Gemini to generate or edit images using natural language. Generated images are returned as `GeneratedImage` objects and can be saved to disk.
405
+
406
+ > Google has limitations on image generation availability that vary by region and account type. Users under 18 cannot use this feature.
211
407
 
212
408
  ```js
213
- // fetch all gems
214
- const gems = await client.fetchGems();
409
+ const response = await client.generateContent({
410
+ prompt: 'Generate a photo-realistic image of a cat in a space suit.',
411
+ });
412
+
413
+ for (let i = 0; i < response.images.length; i++) {
414
+ const image = response.images[i];
415
+ const savedPath = await image.save({
416
+ path: './temp',
417
+ filename: `cat_space_${i}.png`,
418
+ verbose: true,
419
+ });
420
+ console.log(`Saved to: ${savedPath}`);
421
+ }
422
+ ```
215
423
 
216
- // get by name
217
- const gem = gems.get({ name: 'Coding partner' });
424
+ > When asking Gemini to "send" images, it returns web images (`WebImage`). When asking to "generate" images, it returns AI-generated images (`GeneratedImage`). Both are automatically categorized in `response.images`.
218
425
 
219
- // filter user-created gems
220
- const myGems = gems.filter({ predefined: false });
426
+ ### Retrieve Videos and Audio
221
427
 
222
- // use a gem in chat
223
- const chat = client.startChat({ gem: gem });
428
+ Gemini can generate short videos and audio/music. These are returned as `GeneratedVideo` and `GeneratedMedia` objects in `response.videos` and `response.media` respectively.
224
429
 
225
- // create a gem
226
- const newGem = await client.createGem({
227
- name: 'My Assistant',
228
- prompt: 'You are a helpful assistant that speaks formally.',
229
- description: 'Formal assistant gem',
430
+ > You may need an active Gemini subscription to access video and audio generation.
431
+
432
+ ```js
433
+ // Generate a video
434
+ const videoResponse = await client.generateContent({
435
+ prompt: 'Generate a short video of waves on a beach.',
230
436
  });
231
437
 
232
- // update a gem
233
- await client.updateGem({
234
- gem: newGem,
235
- name: 'My Assistant v2',
236
- prompt: 'You are a helpful assistant that speaks casually.',
438
+ for (const video of videoResponse.videos) {
439
+ const result = await video.save({ savePath: './temp', verbose: true });
440
+ console.log('Video:', result.video);
441
+ console.log('Thumbnail:', result.video_thumbnail);
442
+ }
443
+
444
+ // Generate audio/music
445
+ const mediaResponse = await client.generateContent({
446
+ prompt: 'Compose a short calming piano melody.',
237
447
  });
238
448
 
239
- // delete a gem
240
- await client.deleteGem(newGem);
449
+ for (const media of mediaResponse.media) {
450
+ // downloadType: 'audio' | 'video' | 'both' (default)
451
+ const result = await media.save({ savePath: './temp', downloadType: 'both', verbose: true });
452
+ console.log('MP3:', result.audio);
453
+ console.log('MP4:', result.video);
454
+ }
241
455
  ```
242
456
 
243
- ### Close Client
457
+ > `GeneratedMedia.save()` accepts a `downloadType` parameter: `"audio"`, `"video"`, or `"both"` (default). The save method polls automatically if content is still generating (HTTP 206).
458
+
459
+ ### Generate Content with Gemini Extensions
460
+
461
+ To use Gemini extensions (Gmail, YouTube, etc.), you must first activate them on the [Gemini website](https://gemini.google.com/extensions). Reference them in prompts with the `@` prefix or in natural language.
462
+
463
+ > You must have Gemini Apps Activity enabled in your account to use extensions.
244
464
 
245
465
  ```js
246
- await client.close();
466
+ const gmailResponse = await client.generateContent({
467
+ prompt: "@Gmail What's the latest message in my mailbox?",
468
+ });
469
+ console.log(gmailResponse.text);
470
+
471
+ const youtubeResponse = await client.generateContent({
472
+ prompt: "@YouTube What's the latest video from Fireship?",
473
+ });
474
+ console.log(youtubeResponse.text);
247
475
  ```
248
476
 
249
- ---
477
+ ### Check and Switch to Other Reply Candidates
250
478
 
251
- ## TypeScript
479
+ A Gemini response sometimes contains multiple reply candidates with different generated content. You can inspect all candidates and choose one to continue the conversation flow.
252
480
 
253
- This package includes full TypeScript declarations out of the box.
481
+ ```js
482
+ const chat = client.startChat();
483
+ const response = await chat.sendMessage({ prompt: 'Recommend a science fiction book.' });
254
484
 
255
- ```ts
256
- import { GeminiClient, ChatSession, ModelOutput, ConversationTurn, Gem, Model } from 'gemini-api';
485
+ // List all candidates
486
+ response.candidates.forEach((candidate, i) => {
487
+ console.log(`[${i}] ${candidate.text.slice(0, 80)}...`);
488
+ });
257
489
 
258
- const client = new GeminiClient({ secure_1psid: '...' });
259
- await client.init();
490
+ if (response.candidates.length > 1) {
491
+ // Choose the second candidate to continue from
492
+ chat.chooseCandidate(1);
260
493
 
261
- const chat: ChatSession = client.startChat({ model: 'gemini-3.1-pro' });
262
- const response: ModelOutput = await chat.sendMessage({ prompt: 'Hello!' });
494
+ const followup = await chat.sendMessage({ prompt: 'Tell me more about it.' });
495
+ console.log(followup.text);
496
+ } else {
497
+ console.log('Only one candidate available.');
498
+ }
499
+ ```
263
500
 
264
- console.log(response.text);
501
+ ### Deep Research
502
+
503
+ Gemini's deep research feature is an autonomous agent that browses the web, analyzes sources, and produces a comprehensive report.
504
+
505
+ > You may need an active Gemini subscription to access deep research.
506
+
507
+ **Quick one-call method:**
508
+
509
+ ```js
510
+ const result = await client.deepResearch(
511
+ 'Compare the top 3 cloud providers and their AI offerings',
512
+ 10000, // poll interval in ms
513
+ 600000, // timeout in ms
514
+ (status) => console.log(`Status: ${status.state} — ${status.notes.slice(0, 1).join(', ')}`),
515
+ );
516
+
517
+ console.log(`Done: ${result.done}`);
518
+ console.log(result.text);
265
519
  ```
266
520
 
267
- ---
521
+ **Step-by-step workflow** for more control:
268
522
 
269
- ## Project Structure
523
+ ```js
524
+ // Step 1: Create a research plan
525
+ const plan = await client.createDeepResearchPlan(
526
+ 'What are the latest advancements in quantum computing?'
527
+ );
528
+
529
+ console.log(`Title: ${plan.title}`);
530
+ console.log(`ETA: ${plan.eta_text}`);
531
+ for (const step of plan.steps) {
532
+ console.log(` - ${step}`);
533
+ }
270
534
 
535
+ // Step 2: Start the research
536
+ const startOutput = await client.startDeepResearch(plan);
537
+ console.log('Research started:', startOutput.text.slice(0, 100));
538
+
539
+ // Step 3: Poll for completion
540
+ const result = await client.waitForDeepResearch(
541
+ plan,
542
+ 10000, // poll interval in ms
543
+ 600000, // timeout in ms
544
+ (status) => console.log(`[${status.state}] ${status.notes[0] || ''}`),
545
+ );
546
+
547
+ console.log(result.text);
271
548
  ```
272
- gemini-api/
273
- ├── index.js # entry point
274
- ├── index.d.ts # TypeScript declarations
275
- ├── client.js # GeminiClient + ChatSession
276
- ├── constants.js # Endpoint, GRPC, Headers, Model, ErrorCode
277
- ├── exceptions.js # custom error classes
278
- ├── types/
279
- │ ├── candidate.js
280
- │ ├── conversation.js
281
- │ ├── gem.js
282
- │ ├── grpc.js
283
- │ ├── image.js
284
- │ └── modeloutput.js
285
- ├── utils/
286
- │ ├── accessToken.js
287
- │ ├── parsing.js
288
- │ ├── rotate.js
289
- │ └── upload.js
290
- └── components/
291
- ├── chatMixin.js
292
- └── gemMixin.js
549
+
550
+ ### Account Status
551
+
552
+ The client detects your account's capability tier at initialization and exposes it via `client.accountStatus`.
553
+
554
+ ```js
555
+ const { AccountStatus } = require('gemini-reverse');
556
+
557
+ await client.init();
558
+
559
+ if (client.accountStatus === AccountStatus.AVAILABLE) {
560
+ console.log('Account is fully authorized.');
561
+ } else if (client.accountStatus === AccountStatus.UNAUTHENTICATED) {
562
+ console.error('Cookies are expired or invalid.');
563
+ } else if (client.accountStatus === AccountStatus.LOCATION_REJECTED) {
564
+ console.error('Gemini is not available in your region.');
565
+ } else {
566
+ console.warn(`Account status: ${client.accountStatus.name} — ${client.accountStatus.description}`);
567
+ }
293
568
  ```
294
569
 
295
- ---
570
+ **All account status values:**
571
+
572
+ | Constant | Code | Description |
573
+ |---|---|---|
574
+ | `AccountStatus.AVAILABLE` | 1000 | Account is authorized and has normal access |
575
+ | `AccountStatus.ACCESS_TEMPORARILY_UNAVAILABLE` | 1014 | Access restricted, possibly regional/temporary |
576
+ | `AccountStatus.UNAUTHENTICATED` | 1016 | Cookies have expired or are invalid |
577
+ | `AccountStatus.ACCOUNT_REJECTED` | 1021 | Account access rejected |
578
+ | `AccountStatus.ACCOUNT_UNTRUSTED` | 1033 | Did not pass safety/trust checks |
579
+ | `AccountStatus.TOS_PENDING` | 1040 | Must accept latest Terms of Service |
580
+ | `AccountStatus.TOS_OUT_OF_DATE` | 1042 | Terms of Service are out of date |
581
+ | `AccountStatus.ACCOUNT_REJECTED_BY_GUARDIAN` | 1054 | Blocked by parent or guardian |
582
+ | `AccountStatus.GUARDIAN_APPROVAL_REQUIRED` | 1057 | Requires parental approval |
583
+ | `AccountStatus.LOCATION_REJECTED` | 1060 | Not available in your country/region |
296
584
 
297
585
  ## Error Handling
298
586
 
@@ -305,41 +593,125 @@ const {
305
593
  UsageLimitExceeded,
306
594
  ModelInvalid,
307
595
  TemporarilyBlocked,
308
- } = require('gemini-api');
596
+ } = require('gemini-reverse');
309
597
 
310
598
  try {
311
599
  const response = await chat.sendMessage({ prompt: 'Hello!' });
600
+ console.log(response.text);
312
601
  } catch (e) {
313
602
  if (e instanceof AuthError) {
314
- console.error('Cookie expired or invalid.');
603
+ console.error('Cookie expired or invalid. Please refresh your cookies.');
315
604
  } else if (e instanceof UsageLimitExceeded) {
316
- console.error('Usage limit reached. Try again later or switch models.');
605
+ console.error('Usage limit reached. Try again later or switch to a different model.');
317
606
  } else if (e instanceof TemporarilyBlocked) {
318
- console.error('IP temporarily blocked. Try using a proxy.');
607
+ console.error('IP temporarily blocked by Google. Try using a proxy or wait a while.');
319
608
  } else if (e instanceof TimeoutError) {
320
- console.error('Request timed out.');
609
+ console.error('Request timed out. Try increasing the timeout value in init().');
321
610
  } else if (e instanceof ModelInvalid) {
322
- console.error('Invalid or unavailable model.');
611
+ console.error('Invalid or unavailable model. Try a different model.');
323
612
  } else if (e instanceof APIError) {
324
613
  console.error('API error:', e.message);
614
+ } else {
615
+ throw e;
325
616
  }
326
617
  }
327
618
  ```
328
619
 
329
- ---
620
+ ## Cookie Persistence
330
621
 
331
- ## Credits
622
+ If your application runs in a containerized environment (e.g. Docker), you can persist the auto-refreshed cookie cache to a volume by setting the `GEMINI_COOKIE_PATH` environment variable to a writable path.
332
623
 
333
- Inspired by [Gemini-API](https://github.com/HanaokaYuzu/Gemini-API) by [@HanaokaYuzu](https://github.com/HanaokaYuzu) — an unofficial Python client for Gemini through reverse engineering.
624
+ ```yaml
625
+ # docker-compose.yml
626
+ services:
627
+ app:
628
+ environment:
629
+ GEMINI_COOKIE_PATH: /tmp/gemini_cache
630
+ volumes:
631
+ - ./gemini_cookies:/tmp/gemini_cache
632
+ ```
334
633
 
335
- ---
634
+ By default, the cache is stored in `utils/temp/` relative to the package directory.
635
+
636
+ ## TypeScript
637
+
638
+ This package includes full TypeScript declarations.
639
+
640
+ ```ts
641
+ import {
642
+ GeminiClient,
643
+ ChatSession,
644
+ ModelOutput,
645
+ ChatHistory,
646
+ ChatTurn,
647
+ ChatInfo,
648
+ Gem,
649
+ AvailableModel,
650
+ Model,
651
+ AccountStatus,
652
+ DeepResearchPlan,
653
+ DeepResearchResult,
654
+ } from 'gemini-reverse';
655
+
656
+ const client = new GeminiClient({ secure_1psid: '...' });
657
+ await client.init();
658
+
659
+ const chat: ChatSession = client.startChat({ model: 'gemini-3-flash' });
660
+ const response: ModelOutput = await chat.sendMessage({ prompt: 'Hello!' });
661
+
662
+ console.log(response.text);
663
+
664
+ const history: ChatHistory | null = await chat.readHistory();
665
+ if (history) {
666
+ history.turns.forEach((turn: ChatTurn) => console.log(turn.role, turn.text));
667
+ }
668
+
669
+ const models: AvailableModel[] | null = client.listModels();
670
+ ```
671
+
672
+ ## Project Structure
673
+
674
+ ```
675
+ gemini-reverse/
676
+ ├── index.js # entry point & exports
677
+ ├── index.d.ts # TypeScript declarations
678
+ ├── client.js # GeminiClient + ChatSession
679
+ ├── constants.js # Endpoint, GRPC, Headers, Model, AccountStatus, ErrorCode
680
+ ├── exceptions.js # custom error classes
681
+ ├── types/
682
+ │ ├── availablemodel.js # AvailableModel (dynamic model from API)
683
+ │ ├── candidate.js # Candidate
684
+ │ ├── chathistory.js # ChatTurn, ChatHistory
685
+ │ ├── chatinfo.js # ChatInfo
686
+ │ ├── gem.js # Gem, GemJar
687
+ │ ├── grpc.js # RPCData
688
+ │ ├── image.js # Image, WebImage, GeneratedImage
689
+ │ ├── modeloutput.js # ModelOutput
690
+ │ ├── research.js # DeepResearchPlan, DeepResearchStatus
691
+ │ ├── researchresult.js # DeepResearchResult
692
+ │ └── video.js # Video, GeneratedVideo, GeneratedMedia
693
+ ├── utils/
694
+ │ ├── accessToken.js # cookie handling & init request
695
+ │ ├── parsing.js # response parsing utilities
696
+ │ ├── research.js # deep research payload extractors
697
+ │ ├── rotate.js # cookie rotation (kept from original)
698
+ │ └── upload.js # file upload helpers
699
+ └── components/
700
+ ├── chatMixin.js # chat history methods
701
+ ├── gemMixin.js # gem management methods
702
+ └── researchMixin.js # deep research methods
703
+ ```
704
+
705
+ ## References
706
+
707
+ [Google AI Studio](https://ai.google.dev/tutorials/ai-studio_quickstart)
336
708
 
337
- ## Disclaimer
709
+ [Gemini-API (Python)](https://github.com/HanaokaYuzu/Gemini-API) by [@HanaokaYuzu](https://github.com/HanaokaYuzu)
338
710
 
339
- This is an unofficial package and is not affiliated with or endorsed by Google. Use at your own risk. Cookie-based authentication may break if Google changes its internal API.
711
+ [acheong08/Bard](https://github.com/acheong08/Bard)
340
712
 
341
713
  ---
342
714
 
343
- ## License
715
+ **Disclaimer:** This is an unofficial package and is not affiliated with or endorsed by Google. Cookie-based authentication may break if Google changes its internal API. Use at your own risk.
344
716
 
345
- MIT
717
+ **License:** MIT