Skip to content

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

save
DialogueDB

DialogueDB

load

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.

Two calls, that's the integrationsrc/turn.ts
// 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.

1

Install the client

npm install dialogue-db
2

Set both API keys

DIALOGUE_DB_API_KEY=...
GEMINI_API_KEY=...
3

Save each turn

await dialogue.saveMessage({
  role: "assistant",
  content: response.text ?? "",
})
Get Your Free API Key

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.

src/hello-world.ts
import { GoogleGenAI, type Content } from "@google/genai"
import { DialogueDB } from "dialogue-db"
 
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY })
const db = new DialogueDB({ apiKey: process.env.DIALOGUE_DB_API_KEY! })
 
/** Gemini validates every part, so foreign content is serialized. */
function toParts(content: MessageContent): Part[] {
if (typeof content === "string") return [{ text: content }]
const items = Array.isArray(content) ? content : [content]
return items.map((part) => (isGeminiPart(part) ? part : { text: JSON.stringify(part) }))
}
 
/** Gemini takes system prompts separately, never as a turn. */
function toSystemInstruction(dialogue: Dialogue): string | undefined {
const system = dialogue.messages.filter((m) => m.role === "system")
return system.length ? system.map((m) => String(m.content)).join("\n\n") : undefined
}
 
/** Stored messages back to the Content array Gemini takes. */
function toGeminiContents(dialogue: Dialogue): Content[] {
return dialogue.messages
.filter((m) => m.role !== "system") // system goes to systemInstruction
.map((m) => ({
role: m.role === "assistant" ? "model" : "user",
parts: toParts(m.content), // normalizes foreign content
}))
}
 
const dialogue = await db.getOrCreateDialogue({ id, namespace })
await dialogue.loadMessages({ order: "asc" })
await dialogue.saveMessage({ role: "user", content: input })
 
const response = await ai.models.generateContent({
model: MODEL,
contents: toGeminiContents(dialogue),
config: { systemInstruction: toSystemInstruction(dialogue) },
})
 
await dialogue.saveMessage({
role: "assistant",
content: response.text ?? "",
metadata: {
promptTokenCount: response.usageMetadata?.promptTokenCount ?? 0,
candidatesTokenCount: response.usageMetadata?.candidatesTokenCount ?? 0,
},
})

Where DialogueDB fits

Four ways to keep a Gemini conversation, and what each one asks of you.

ApproachSurvives a restartKeeps function call parts intactMemory and search included
The SDK chat objectNoYes, until the process endsNo
Your own tableYesIf you model JSON columns for itAdd a vector store and a pipeline
Standalone memory serviceYes, for extracted factsRarely, transcripts are summarizedMemory yes, full transcript no
DialogueDBYesYes, stored as sentYes, 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 SDKDialogueDB
contents: Content[] you pass to generateContentdialogue.loadMessages({ order: 'asc' }) rebuilds it
chats.create({ history })Same rebuilt array, handed in as history
Contentmessage, stored as sent
parts array (text, functionCall, functionResponse)Kept as an array, exactly as returned
role: "model"Same role field (see note below)
systemInstructionComposed 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

Google

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.