Google Gemini Integration
Gemini chat history that outlives the process
A Gemini chat keeps its turns in memory, and the stateless call takes the whole history every time. DialogueDB is where that history lives in between.
Out of the SDK
DialogueDB
Back into the SDK
How it works
Your Gemini conversation, saved between calls
Save each turn to a dialogue, load it back on the next request. Works with ai.chats.create as history, or generateContent as contents.
// Save every turn as it happens
await dialogue.saveMessage({ role: "user", content: input })
await dialogue.saveMessage({ role: "assistant", content: response.text ?? "" })
// Load it back on the next request
await dialogue.loadMessages({ order: "asc" })
const contents = toGeminiContents(dialogue)What else DialogueDB adds
Three more things a Gemini app runs into next
All handled by the same client. No second service, no vector database.
Every chat stays isolated per user
Pass a namespace on every read and write. Two users can share the same dialogue id and never see each other, with no tenant column in your app.
Returning users skip the intro
Facts worth keeping live as Memory objects, separate from any single chat. The next session with the same user opens with what you chose to remember.
Users find things they said weeks ago
Search stored messages and memories by meaning, not keyword. No embeddings pipeline, no vector database to run alongside your app.
The one helper you write
Three Gemini-specific bits, handled once and forgotten
Stored messages come out in the exact shape Gemini takes. Here are the three small differences one helper bridges.
Role names
Gemini calls the assistant model. The helper renames it on the way out so the SDK is happy.
System prompts
Gemini takes the system prompt as its own parameter, not as a turn. The helper pulls system messages aside and hands them to systemInstruction.
Content shape
Gemini expects a parts array on every turn. The helper wraps text as a part and keeps stored functionCall and functionResponse parts as they were.
The helper is one short function. See it in the reference example below, or let your coding assistant drop it in.
The same client does more
Six more things the same install covers, without another SDK to add.
Continue a chat on any worker
Load the dialogue, hand it to chats.create as history, and the session picks up wherever the request lands.
ai.chats.create({ history })Facts and preferences across sessions
Store what the assistant should remember about a user, independent of any single chat.
db.createMemory()Recall by meaning before you call Gemini
Search stored memories against the new turn and put the matches in the system instruction.
db.searchMemories()Token counts alongside the message
Keep usageMetadata on the message it belongs to, so cost per conversation is a query rather than a guess.
metadata: { promptTokenCount }Branch a chat for a sub-task
Parent and child dialogues let a delegated task keep its own transcript without polluting the main thread.
dialogue.createThread()Per-conversation scratchpad
Give each chat its own state object for workflow flags, partial results, or in-progress context.
dialogue.saveState()Install in 3 steps
From npm install to the first persisted turn, in under five minutes.
Install the client
npm install dialogue-dbSet both API keys
DIALOGUE_DB_API_KEY=...
GEMINI_API_KEY=...Save each turn
await dialogue.saveMessage({
role: "assistant",
content: response.text ?? "",
})No credit card. Free tier to start. Starter is $29/month when usage grows.
The reference example
A hello-world turn function that survives a restart, plus an advanced example where a function-calling loop resumes in a fresh process from storage alone.
Runs on Edge and serverless too. The client is HTTP-only, with no connection pool and no native dependencies.
Hello world
Chat that survives a restart
Two exchanges, a cold reload from DialogueDB, then a third turn that proves Gemini still has the context from before the restart.
Open the exampleAdvanced
Function calling across processes
A manual function-calling loop where every functionCall and functionResponse part is persisted, then resumed in a second process from storage alone.
Open the exampleWhere DialogueDB fits
Four ways to keep a Gemini conversation, and what each one asks of you.
| Approach | Survives a restart | Keeps function call parts intact | Memory and search included |
|---|---|---|---|
| The SDK chat object | No | Yes, until the process ends | No |
| Your own table | Yes | If you model JSON columns for it | Add a vector store and a pipeline |
| Standalone memory service | Yes, for extracted facts | Rarely, transcripts are summarized | Memory yes, full transcript no |
| DialogueDB | Yes | Yes, stored as sent | Yes, one API |
If you know Content and parts, you already know this
Everything nests under a namespace, and the concepts line up with the shapes the Gemini SDK already hands you.
The data model
namespace
scoped to one user or tenant
dialogue
messages, threads, state
indexed for semantic search
memory
facts and preferences, cross-session
indexed for semantic search
| Google GenAI SDK | DialogueDB |
|---|---|
contents: Content[] you pass to generateContent | dialogue.loadMessages({ order: 'asc' }) rebuilds it |
chats.create({ history }) | Same rebuilt array, handed in as history |
Content | message, stored as sent |
parts array (text, functionCall, functionResponse) | Kept as an array, exactly as returned |
role: "model" | Same role field (see note below) |
systemInstruction | Composed from db.searchMemories() at request time |
usageMetadata (promptTokenCount, candidatesTokenCount) | metadata on the assistant message |
A DialogueDB role is a free-form string, not a fixed enum, so the model spelling stores fine if you prefer to keep Gemini's own vocabulary end to end.
What DialogueDB sees
Only what's needed for conversation storage
Your call to Gemini goes straight from your server to Google. DialogueDB sits beside that request, never inside it.
The client only receives what you explicitly send: message content, memory values, and the identifiers you scope them with. Your Gemini key, your user tokens, and everything else in your app never pass through it.
What we receive
- Message parts and roles
- Memory values and metadata
- Dialogue and namespace IDs
What we never see
- Your Gemini API key
- User authentication tokens
- Your system instructions, tool definitions, or model config
Two independent calls from your server
Your server
src/turn.ts
Your code decides what goes where.
nothing routed through us
generativelanguage.googleapis.com
The model call goes direct. DialogueDB never sees the request or the credential.
DialogueDB
Messages and memory only
Receives what you explicitly save. Nothing implicit, nothing extra.
Frequently asked questions
One API for messages, memory, and search
Add it to your Gemini app in minutes.