gemini-reverse
v1.0.12
Published
Unofficial Node.js client for gemini.google.com — inspired by Gemini-API (Python). Supports streaming, chat sessions, gems, file uploads, and TypeScript.
Maintainers
Readme

Gemini-Reverse
An unofficial Node.js client for Google Gemini, inspired by Gemini-API — a Python reverse engineering project by @HanaokaYuzu.
Language / Bahasa: English · Indonesia | Changelog: EN · IDN
Features
- Persistent Cookies — Automatically refreshes cookies in the background with jitter to prevent synchronized requests. Optimized for always-on services.
- Image Generation — Natively supports generating and editing images with natural language.
- Video & Audio Generation — Supports generating videos and audio/music content natively.
- Deep Research — Full deep research workflow with plan creation, status polling, and result retrieval.
- Extended Thinking — Enables deeper reasoning mode on supported models.
- System Prompt — Supports customizing the model's system prompt with Gemini Gems.
- Extension Support — Supports generating content with Gemini extensions such as YouTube and Gmail.
- Classified Outputs — Categorizes text, thoughts, images, videos, and audio in the response.
- Streaming Mode — Supports stream generation with an incremental stateful frame parser, yielding partial outputs as they are generated.
- Dynamic Model Discovery — Automatically discovers available models from your account at initialization.
- Quota & Usage Info — Exposes account quota, compute usage, and abuse status after initialization.
- Activity Watchdog — Background heartbeat task that keeps the session alive automatically.
- TypeScript Support — Full TypeScript type declarations included out of the box.
Table of Contents
- Features
- Table of Contents
- Installation
- Authentication
- Usage
- Initialization
- Generate Content
- Generate Content with Files
- Conversations Across Multiple Turns
- Continue Previous Conversations
- Read Conversation History
- List Recent Chats
- Delete a Conversation
- Temporary Mode
- Streaming Mode
- Extended Thinking
- Select Language Model
- List Available Models
- Apply System Prompt with Gemini Gems
- Manage Custom Gems
- Retrieve Model's Thought Process
- Retrieve Images in Response
- Generate and Edit Images
- Retrieve Videos and Audio
- Generate Content with Gemini Extensions
- Check and Switch to Other Reply Candidates
- Deep Research
- Account Status
- Quota and Usage Info
- Error Handling
- Cookie Persistence
- TypeScript
- Project Structure
- References
Installation
npm install gemini-reverseAuthentication
- Go to gemini.google.com and log in with your Google account
- Press F12 to open DevTools, go to the
Applicationtab →Cookies→https://gemini.google.com - Copy the value of
__Secure-1PSID(and optionally__Secure-1PSIDTS)
__Secure-1PSIDTSis optional — the client will attempt to refresh and cache it automatically after the first successful initialization.
Usage
Initialization
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.
const { GeminiClient } = require('gemini-reverse');
const client = new GeminiClient({
secure_1psid: 'YOUR_SECURE_1PSID',
secure_1psidts: 'YOUR_SECURE_1PSIDTS', // optional
proxy: null, // optional, e.g. 'http://host:port'
});
await client.init({
timeout: 300000, // request timeout in ms, default 300000
autoClose: false, // auto-close client after inactivity
closeDelay: 300000, // inactivity delay before closing in ms
autoRefresh: true, // auto-refresh cookies + start activity watchdog
refreshInterval: 540000 // cookie refresh interval in ms
});
autoCloseandcloseDelayare optional arguments for automatically closing the client after a period of inactivity. In an always-on service like a chatbot, it is recommended to setautoClosetotruewith a reasonablecloseDelayvalue for better resource management.
Generate Content
Ask a single-turn question by calling generateContent, which returns a ModelOutput object containing the generated text, images, thoughts, and conversation metadata.
const response = await client.generateContent({ prompt: 'Hello World!' });
console.log(response.text);Generate Content with Files
Gemini supports file input, including images and documents. Pass an array of file paths or Buffer objects alongside your text prompt.
const response = await client.generateContent({
prompt: 'Describe the contents of these files.',
files: ['./document.pdf', './photo.png'],
});
console.log(response.text);Conversations Across Multiple Turns
Use startChat to create a ChatSession object and send messages through it. The conversation history is handled automatically and updated after each turn.
const chat = client.startChat();
const res1 = await chat.sendMessage({ prompt: 'My name is Alice.' });
console.log(res1.text);
const res2 = await chat.sendMessage({ prompt: 'What is my name?' });
console.log(res2.text); // remembers contextContinue Previous Conversations
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.
const chat = client.startChat();
await chat.sendMessage({ prompt: 'Fine weather today.' });
// Save the session metadata
const savedMetadata = chat.metadata;
const savedCid = chat.cid;
// Resume in a new session
const previousChat = client.startChat({ metadata: savedMetadata });
const response = await previousChat.sendMessage({ prompt: 'What was my previous message?' });
console.log(response.text);Read Conversation History
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.
const chat = client.startChat();
await chat.sendMessage({ prompt: 'What is the capital of France?' });
const history = await client.readChat(chat.cid);
if (history) {
for (const turn of history.turns) {
console.log(`[${turn.role.toUpperCase()}] ${turn.text}`);
}
}You can also read the history directly from the session:
const history = await chat.readHistory(10); // fetch last 10 turnsList Recent Chats
Use listChats to get a list of recent chat sessions cached at initialization.
const chats = client.listChats();
if (chats) {
for (const info of chats) {
console.log(`${info.cid}: ${info.title} (pinned: ${info.is_pinned})`);
}
}Delete a Conversation
Delete a specific chat from Gemini history on the server by calling deleteChat with the chat ID.
const chat = client.startChat();
await chat.sendMessage({ prompt: 'This is a temporary conversation.' });
await client.deleteChat(chat.cid);
console.log(`Chat deleted: ${chat.cid}`);Temporary Mode
Pass temporary: true to generateContent or sendMessage to prevent the conversation from being saved to Gemini history.
const response = await client.generateContent({
prompt: 'Hello World!',
temporary: true,
});
console.log(response.text);
// Also works in chat sessions
const chat = client.startChat();
await chat.sendMessage({ prompt: 'Fine weather today.', temporary: false });
const res2 = await chat.sendMessage({ prompt: "What's my last message?", temporary: true });
console.log(res2.text);Streaming Mode
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.
for await (const chunk of client.generateContentStream({
prompt: "What's the difference between 'await' and 'async for'?",
})) {
process.stdout.write(chunk.text_delta);
}
console.log();Streaming also works inside a chat session:
const chat = client.startChat();
for await (const chunk of chat.sendMessageStream({ prompt: 'Tell me a long story.' })) {
process.stdout.write(chunk.text_delta);
}Extended Thinking
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.
const { Model } = require('gemini-reverse');
const response = await client.generateContent({
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?',
model: Model.ADVANCED_PRO,
extended_thinking: true,
});
if (response.thoughts) {
console.log('Thinking process:', response.thoughts);
}
console.log('Answer:', response.text);Also works in streaming and chat:
const chat = client.startChat({ model: Model.ADVANCED_FLASH });
const res = await chat.sendMessage({
prompt: 'Explain the P vs NP problem.',
extended_thinking: true,
});
console.log(res.text);Select Language Model
Specify which language model to use by passing a model argument. Available models are discovered dynamically at init time based on your account tier.
const { Model } = require('gemini-reverse');
// Using a built-in constant
const response1 = await client.generateContent({
prompt: 'What is your model version?',
model: Model.BASIC_FLASH,
});
// Using a model name string (case-insensitive)
const chat = client.startChat({ model: 'gemini-3-pro' });
// Using a custom model header dict
const chat2 = client.startChat({
model: {
model_name: 'custom',
model_header: {
'x-goog-ext-525001261-jspb': '[1,null,null,null,"MODEL_ID",null,null,0,[4,5,6,8],null,null,1,null,null,1]',
'x-goog-ext-73010989-jspb': '[0]',
'x-goog-ext-73010990-jspb': '[0,0,0]',
},
},
});Built-in model constants:
| Constant | model_name | Notes |
|---|---|---|
| Model.UNSPECIFIED | unspecified | Default, lets Gemini choose |
| Model.BASIC_PRO | gemini-3-pro | Free tier |
| Model.BASIC_FLASH | gemini-3-flash | Free tier, fastest |
| Model.BASIC_LITE | gemini-3-lite | Free tier, lightweight |
| Model.PLUS_PRO | gemini-3-pro-plus | Plus tier |
| Model.PLUS_FLASH | gemini-3-flash-plus | Plus tier |
| Model.PLUS_LITE | gemini-3-lite-plus | Plus tier |
| Model.ADVANCED_PRO | gemini-3-pro-advanced | Advanced tier |
| Model.ADVANCED_FLASH | gemini-3-flash-advanced | Advanced tier |
| Model.ADVANCED_LITE | gemini-3-lite-advanced | Advanced tier |
List Available Models
The client dynamically discovers which models your account can access during initialization. Use listModels to inspect them.
await client.init();
const models = client.listModels();
if (models) {
for (const model of models) {
console.log(`${model.model_id} → ${model.model_name || model.display_name}`);
console.log(` capacity: ${model.capacity}, model_number: ${model.model_number}, advanced_only: ${model.advanced_only}`);
}
}Apply System Prompt with Gemini Gems
System prompts can be applied to conversations via Gemini Gems. Pass the gem argument to generateContent or startChat — it can be a Gem object or a gem ID string.
// Fetch all gems for the account
await client.fetchGems();
const gems = client.gems;
// Get a specific gem
const codingPartner = gems.get({ name: 'Coding partner' });
const response = await client.generateContent({
prompt: "What's your system prompt?",
gem: codingPartner,
});
console.log(response.text);
// Use a gem in a multi-turn chat
const chat = client.startChat({ gem: codingPartner });
const res2 = await chat.sendMessage({ prompt: 'Help me write a binary search.' });
console.log(res2.text);There are some system predefined gems that are hidden by default. Use
fetchGems({ includeHidden: true })to include them.
Manage Custom Gems
You can create, update, and delete custom gems programmatically. Predefined system gems cannot be modified.
Create a Custom Gem
const newGem = await client.createGem({
name: 'Python Tutor',
prompt: 'You are a helpful Python programming tutor. Always provide runnable code examples.',
description: 'A specialized gem for Python programming',
});
console.log(`Created: ${newGem.id}`);
// Use the new gem immediately
const response = await client.generateContent({
prompt: 'Explain list comprehensions.',
gem: newGem,
});
console.log(response.text);Update an Existing Gem
When updating a gem, all parameters (
name,prompt,description) must be provided even if only one is changing.
await client.fetchGems();
const pythonTutor = client.gems.get({ name: 'Python Tutor' });
const updatedGem = await client.updateGem({
gem: pythonTutor,
name: 'Advanced Python Tutor',
prompt: 'You are an expert Python programming tutor. Focus on performance and best practices.',
description: 'An advanced Python programming assistant',
});
console.log(`Updated: ${updatedGem.id}`);Delete a Custom Gem
await client.fetchGems();
const gemToDelete = client.gems.get({ name: 'Advanced Python Tutor' });
await client.deleteGem(gemToDelete); // can also pass a gem ID string
console.log(`Deleted: ${gemToDelete.name}`);Retrieve Model's Thought Process
When using thinking-capable models, the model's internal reasoning is exposed via response.thoughts.
const response = await client.generateContent({
prompt: 'What is 17 × 23?',
model: Model.BASIC_FLASH,
});
if (response.thoughts) {
console.log('Thoughts:', response.thoughts);
}
console.log('Answer:', response.text);Retrieve Images in Response
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.
const response = await client.generateContent({ prompt: 'Send me some pictures of cats.' });
for (const image of response.images) {
console.log(`${image.title}: ${image.url}`);
console.log(`Alt: ${image.alt}`);
}Generate and Edit Images
Ask Gemini to generate or edit images using natural language. Generated images are returned as GeneratedImage objects and can be saved to disk.
Google has limitations on image generation availability that vary by region and account type. Users under 18 cannot use this feature.
const response = await client.generateContent({
prompt: 'Generate a photo-realistic image of a cat in a space suit.',
});
for (let i = 0; i < response.images.length; i++) {
const image = response.images[i];
const savedPath = await image.save({
path: './temp',
filename: `cat_space_${i}.png`,
verbose: true,
});
console.log(`Saved to: ${savedPath}`);
}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 inresponse.images.
Retrieve Videos and Audio
Gemini can generate short videos and audio/music. These are returned as GeneratedVideo and GeneratedMedia objects in response.videos and response.media respectively.
You may need an active Gemini subscription to access video and audio generation.
// Generate a video
const videoResponse = await client.generateContent({
prompt: 'Generate a short video of waves on a beach.',
});
for (const video of videoResponse.videos) {
const result = await video.save({ savePath: './temp', verbose: true });
console.log('Video:', result.video);
console.log('Thumbnail:', result.video_thumbnail);
}
// Generate audio/music
const mediaResponse = await client.generateContent({
prompt: 'Compose a short calming piano melody.',
});
for (const media of mediaResponse.media) {
// downloadType: 'audio' | 'video' | 'both' (default)
const result = await media.save({ savePath: './temp', downloadType: 'both', verbose: true });
console.log('MP3:', result.audio);
console.log('MP4:', result.video);
}
GeneratedMedia.save()accepts adownloadTypeparameter:"audio","video", or"both"(default). The save method polls automatically if content is still generating (HTTP 206).
Generate Content with Gemini Extensions
To use Gemini extensions (Gmail, YouTube, etc.), you must first activate them on the Gemini website. Reference them in prompts with the @ prefix or in natural language.
You must have Gemini Apps Activity enabled in your account to use extensions.
const gmailResponse = await client.generateContent({
prompt: "@Gmail What's the latest message in my mailbox?",
});
console.log(gmailResponse.text);
const youtubeResponse = await client.generateContent({
prompt: "@YouTube What's the latest video from Fireship?",
});
console.log(youtubeResponse.text);Check and Switch to Other Reply Candidates
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.
const chat = client.startChat();
const response = await chat.sendMessage({ prompt: 'Recommend a science fiction book.' });
// List all candidates
response.candidates.forEach((candidate, i) => {
console.log(`[${i}] ${candidate.text.slice(0, 80)}...`);
});
if (response.candidates.length > 1) {
// Choose the second candidate to continue from
chat.chooseCandidate(1);
const followup = await chat.sendMessage({ prompt: 'Tell me more about it.' });
console.log(followup.text);
} else {
console.log('Only one candidate available.');
}Deep Research
Gemini's deep research feature is an autonomous agent that browses the web, analyzes sources, and produces a comprehensive report.
You may need an active Gemini subscription to access deep research.
Quick one-call method:
const result = await client.deepResearch(
'Compare the top 3 cloud providers and their AI offerings',
10000, // poll interval in ms
600000, // timeout in ms
(status) => console.log(`Status: ${status.state} — ${status.notes.slice(0, 1).join(', ')}`),
);
console.log(`Done: ${result.done}`);
console.log(result.text);Step-by-step workflow for more control:
// Step 1: Create a research plan
const plan = await client.createDeepResearchPlan(
'What are the latest advancements in quantum computing?'
);
console.log(`Title: ${plan.title}`);
console.log(`ETA: ${plan.eta_text}`);
for (const step of plan.steps) {
console.log(` - ${step}`);
}
// Step 2: Start the research
const startOutput = await client.startDeepResearch(plan);
console.log('Research started:', startOutput.text.slice(0, 100));
// Step 3: Poll for completion
const result = await client.waitForDeepResearch(
plan,
10000, // poll interval in ms
600000, // timeout in ms
(status) => console.log(`[${status.state}] ${status.notes[0] || ''}`),
);
console.log(result.text);Account Status
The client detects your account's capability tier at initialization and exposes it via client.accountStatus.
const { AccountStatus } = require('gemini-reverse');
await client.init();
if (client.accountStatus === AccountStatus.AVAILABLE) {
console.log('Account is fully authorized.');
} else if (client.accountStatus === AccountStatus.UNAUTHENTICATED) {
console.error('Cookies are expired or invalid.');
} else if (client.accountStatus === AccountStatus.LOCATION_REJECTED) {
console.error('Gemini is not available in your region.');
} else {
console.warn(`Account status: ${client.accountStatus.name} — ${client.accountStatus.description}`);
}All account status values:
| Constant | Code | Description |
|---|---|---|
| AccountStatus.AVAILABLE | 1000 | Account is authorized and has normal access |
| AccountStatus.ACCESS_TEMPORARILY_UNAVAILABLE | 1014 | Access restricted, possibly regional/temporary |
| AccountStatus.UNAUTHENTICATED | 1016 | Cookies have expired or are invalid |
| AccountStatus.ACCOUNT_REJECTED | 1021 | Account access rejected |
| AccountStatus.ACCOUNT_UNTRUSTED | 1033 | Did not pass safety/trust checks |
| AccountStatus.TOS_PENDING | 1040 | Must accept latest Terms of Service |
| AccountStatus.TOS_OUT_OF_DATE | 1042 | Terms of Service are out of date |
| AccountStatus.ACCOUNT_REJECTED_BY_GUARDIAN | 1054 | Blocked by parent or guardian |
| AccountStatus.GUARDIAN_APPROVAL_REQUIRED | 1057 | Requires parental approval |
| AccountStatus.LOCATION_REJECTED | 1060 | Not available in your country/region |
Quota and Usage Info
After initialization, the client fetches account quota limits and compute usage metrics automatically. Access them via client.quotas, client.usageInfo, and client.abuseStatus.
await client.init();
// Quota limits (Flash, Pro, extra features)
const quotas = client.quotas;
for (const [id, q] of Object.entries(quotas)) {
if (q.label) {
const remaining = q.remaining !== null ? `${q.remaining}/${q.total}` : 'unlimited';
console.log(`${q.label}: ${remaining} credits (${q.usage_percentage?.toFixed(1) ?? '?'}% used)`);
}
}
// Compute usage (5-hour and weekly windows)
const usage = client.usageInfo;
if (usage.tier) {
console.log(`Account tier: ${usage.tier.label}`);
}
if (usage.current_5h) {
console.log(`5h window: ${usage.current_5h.remaining_credits} credits remaining (${usage.current_5h.usage_percentage}% used)`);
}
if (usage.weekly) {
console.log(`Weekly: ${usage.weekly.remaining_credits} credits remaining`);
}
// Abuse status
const abuse = client.abuseStatus;
if (abuse) {
console.log(`Account clean: ${abuse.is_clean}`);
}Error Handling
const {
AuthError,
APIError,
GeminiError,
TimeoutError,
UsageLimitExceeded,
ModelInvalid,
TemporarilyBlocked,
} = require('gemini-reverse');
try {
const response = await chat.sendMessage({ prompt: 'Hello!' });
console.log(response.text);
} catch (e) {
if (e instanceof AuthError) {
console.error('Cookie expired or invalid. Please refresh your cookies.');
} else if (e instanceof UsageLimitExceeded) {
console.error('Usage limit reached. Try again later or switch to a different model.');
} else if (e instanceof TemporarilyBlocked) {
console.error('IP temporarily blocked by Google. Try using a proxy or wait a while.');
} else if (e instanceof TimeoutError) {
console.error('Request timed out. Try increasing the timeout value in init().');
} else if (e instanceof ModelInvalid) {
console.error('Invalid or unavailable model. Try a different model.');
} else if (e instanceof APIError) {
console.error('API error:', e.message);
} else {
throw e;
}
}Cookie Persistence
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.
# docker-compose.yml
services:
app:
environment:
GEMINI_COOKIE_PATH: /tmp/gemini_cache
volumes:
- ./gemini_cookies:/tmp/gemini_cacheBy default, the cache is stored in utils/temp/ relative to the package directory.
TypeScript
This package includes full TypeScript declarations.
import {
GeminiClient,
ChatSession,
ModelOutput,
ChatHistory,
ChatTurn,
ChatInfo,
Gem,
AvailableModel,
Model,
AccountStatus,
DeepResearchPlan,
DeepResearchResult,
StreamingFrameParser,
} from 'gemini-reverse';
const client = new GeminiClient({ secure_1psid: '...' });
await client.init();
const chat: ChatSession = client.startChat({ model: 'gemini-3-flash' });
const response: ModelOutput = await chat.sendMessage({
prompt: 'Hello!',
extended_thinking: false,
});
console.log(response.text);
const history: ChatHistory | null = await chat.readHistory();
if (history) {
history.turns.forEach((turn: ChatTurn) => console.log(turn.role, turn.text));
}
const models: AvailableModel[] | null = client.listModels();
// Access quota and usage after init
console.log(client.quotas);
console.log(client.usageInfo);
console.log(client.abuseStatus);Project Structure
gemini-reverse/
├── index.js # entry point & exports
├── index.d.ts # TypeScript declarations
├── client.js # GeminiClient + ChatSession
├── constants.js # Endpoint, GRPC, Headers, Model, AccountStatus, ErrorCode
├── exceptions.js # custom error classes
├── types/
│ ├── availablemodel.js # AvailableModel (dynamic model from API)
│ ├── candidate.js # Candidate
│ ├── chathistory.js # ChatTurn, ChatHistory
│ ├── chatinfo.js # ChatInfo
│ ├── gem.js # Gem, GemJar
│ ├── grpc.js # RPCData
│ ├── image.js # Image, WebImage, GeneratedImage
│ ├── modeloutput.js # ModelOutput
│ ├── research.js # DeepResearchPlan, DeepResearchStatus
│ ├── researchresult.js # DeepResearchResult
│ └── video.js # Video, GeneratedVideo, GeneratedMedia
├── utils/
│ ├── accessToken.js # cookie handling & init request
│ ├── parsing.js # response parsing + StreamingFrameParser
│ ├── research.js # deep research payload extractors
│ ├── rotate.js # cookie rotation
│ └── upload.js # file upload helpers
└── components/
├── chatMixin.js # chat history methods
├── gemMixin.js # gem management methods
└── researchMixin.js # deep research methodsReferences
Gemini-API (Python) by @HanaokaYuzu
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.
License: MIT
