VERCEL AI SDK · TYPESCRIPT

Vercel AI SDK Integration

Add persistent, decay-weighted memory to any Vercel AI SDK app. @dakera-ai/ai-sdk plugs into the SDK's two standard extension points — language model middleware and tools — so cross-session memory drops in without changing your model or provider code.

Package: @dakera-ai/ai-sdk  ·  v0.1.0  ·  MIT  ·  GitHub →  ·  npm →

Overview

The Vercel AI SDK gives you a clean, provider-agnostic surface — generateText, streamText, tools — but every call is stateless. Close the process and the model forgets everything a user told it. @dakera-ai/ai-sdk backs that surface with a self-hosted Dakera memory server so relevant facts from earlier sessions are recalled automatically, and new exchanges are written back — with importance-scored, decay-weighted recall so stale context stops competing with fresh, relevant facts.

The package exports exactly two entry points, mirroring the AI SDK's two extension points:

  • createDakeraMemoryMiddleware — language model middleware for transparent recall + write-back. Wrap once, then keep calling generateText / streamText as before.
  • createDakeraTools — a ToolSet of recallMemory / storeMemory tools that hand memory control to the model, so it decides when to look something up or persist a fact.

When to use which

Use caseReach for
Continuity should "just work" with no prompt changesMiddleware
Every turn should recall context and store the exchangeMiddleware
The model should decide when a fact is worth rememberingTools
Agentic / multi-step flows where explicit recall improves qualityTools
Transparent recall, but deliberate writesBoth (middleware with store: false + tools)
HOW DAKERA MEMORY FLOWS THROUGH THE VERCEL AI SDK User turn the new message Dakera recall rank by importance × recency × relevance → top-K memories AI SDK model middleware: transformParams recalls wrapGenerate stores memories injected → LLM Reply write-back: the new exchange is stored for next time

With the middleware, recall happens in transformParams and write-back in wrapGenerate — so generateText and streamText gain cross-session memory with no change to your provider code.

Quick Start

1

Run Dakera

Dakera is a self-hosted memory server with zero external dependencies. The Docker Compose setup brings up the server plus MinIO object storage, listening on :3000:

git clone https://github.com/dakera-ai/dakera-deploy
cd dakera-deploy
docker compose up -d

# server + MinIO object storage, listening on :3000

See dakera-ai/dakera-deploy for the full Compose configuration.

2

Install

npm install @dakera-ai/ai-sdk ai @dakera-ai/dakera zod

Requirements: Node ≥ 20, and the peer dependencies ai ≥ 6.0.0, @dakera-ai/dakera ≥ 0.11.54, and zod ≥ 3.23.0. Bring your own model provider (e.g. @ai-sdk/openai).

3

Configure

Both helpers resolve their connection from options first, then environment variables, then a local default (http://localhost:3000):

export DAKERA_URL=http://localhost:3000   # default — can omit
export DAKERA_API_KEY=dk-your-key-here

How it works

The integration hooks into the two documented AI SDK extension points and nothing else — no forked provider, no patched runtime.

The middleware (transparent path)

createDakeraMemoryMiddleware returns a language model middleware object (specificationVersion: "v3") with two hooks:

  • transformParams — before the model runs, it reads the text of the most recent user message and calls recall(agentId, query, { top_k: recallK, min_importance: minImportance }). If any memories come back, it prepends a single system message to the prompt: a header line followed by one - <content> bullet per memory. If there's no user text, or recall returns nothing, the prompt is passed through untouched. This hook runs for both generateText and streamText.
  • wrapGenerate — after generation completes, it writes the exchange back: User: <the user's message> and Assistant: <the reply>, each stored at the configured importance. This hook runs for generateText-style generate calls.
Recall vs. write-back on streaming. Recall injection lives in transformParams, so it applies to streamText too. Automatic write-back lives in wrapGenerate, which covers generate calls — to persist streamed turns, use the tools pattern or store explicitly (see below).

The tools (model-driven path)

createDakeraTools returns an AI SDK ToolSet with two zod-typed tools the model can call:

  • recallMemory({ query, topK? }) → runs recall and returns an array of { content, importance, score }.
  • storeMemory({ content, importance? }) → persists a fact and returns { id, status: "stored" }.

Pattern 1 — Memory middleware (transparent)

createDakeraMemoryMiddleware wraps any language model with wrapLanguageModel. On every call it recalls the most relevant memories, injects them as a system message, then stores the exchange. Nothing about your generation code changes.

import { generateText, wrapLanguageModel } from "ai";
import { openai } from "@ai-sdk/openai";
import { createDakeraMemoryMiddleware } from "@dakera-ai/ai-sdk";

const model = wrapLanguageModel({
  model: openai("gpt-4o-mini"),
  middleware: createDakeraMemoryMiddleware({
    agentId: "example-user-1",   // Scope memories to a user or agent ID
    recallK: 5,                  // Recall up to 5 relevant memories per call
    importance: 0.8,             // Importance assigned to stored exchanges
  }),
});

// Session 1 — the exchange is stored automatically
await generateText({
  model,
  prompt: "My name is Alex and I'm building a KV store called Kestrel in Go.",
});

// Session 2 — a later run recalls the earlier context, no prompt hints
const { text } = await generateText({
  model,
  prompt: "What do you know about my current project?",
});
// → references Kestrel and Alex without any context in the prompt

What gets injected

When recall returns memories, the middleware prepends one system message shaped like this (the header is configurable via the header option):

# injected as a leading system message
Relevant memories from previous sessions (use them if helpful):
- User: My name is Alex and I'm building a KV store called Kestrel in Go.
- Assistant: Nice — a Go KV store called Kestrel. Want help with the storage engine?

Streaming with streamText

The wrapped model streams exactly like an unwrapped one — recall injection runs transparently in transformParams before the first token. Because automatic write-back runs in wrapGenerate, pair streaming with the tools pattern (or a manual storeMemory call) when you want streamed turns persisted.

import { streamText, wrapLanguageModel } from "ai";
import { openai } from "@ai-sdk/openai";
import { createDakeraMemoryMiddleware } from "@dakera-ai/ai-sdk";

const model = wrapLanguageModel({
  model: openai("gpt-4o-mini"),
  middleware: createDakeraMemoryMiddleware({ agentId: "example-user-1" }),
});

const result = streamText({
  model,
  prompt: "Remind me what I'm working on, then suggest a next step.",
});

for await (const chunk of result.textStream) {
  process.stdout.write(chunk);   // recall already applied before streaming began
}

Pattern 2 — Memory tools (model-driven)

createDakeraTools returns an AI SDK ToolSet with two tools — recallMemory and storeMemory — that the model decides when to call. Spread the result straight into the tools field.

import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { createDakeraTools } from "@dakera-ai/ai-sdk";

const tools = createDakeraTools({
  agentId: "example-user-2",
  recallK: 5,        // default topK when the model omits it
  importance: 0.8, // default importance when the model omits it
});

// The model calls storeMemory(...) to persist the preference
await generateText({
  model: openai("gpt-4o-mini"),
  tools,
  maxSteps: 3,
  prompt: "Please remember that I always want code examples in TypeScript.",
});

// A later call: the model calls recallMemory(...) before answering
await generateText({
  model: openai("gpt-4o-mini"),
  tools,
  maxSteps: 3,
  system: "Always check memory for user preferences before answering.",
  prompt: "Show me a quick example of how to read a file.",
});

The tools return structured results the model can reason over. recallMemory yields [{ content, importance, score }, …]; storeMemory yields { id, status: "stored" }. Provide a topK to a recallMemory call to override the default per-lookup, or an importance (0–1) to a storeMemory call to weight a critical fact.

Pattern 3 — Combined (transparent + explicit)

Use the middleware for automatic continuity and the tools for deliberate writes in the same call. Set store: false on the middleware so it only recalls, and let the tools own persistence. Share one DakeraClient so both hit the same connection.

import { generateText, wrapLanguageModel } from "ai";
import { openai } from "@ai-sdk/openai";
import { createDakeraMemoryMiddleware, createDakeraTools } from "@dakera-ai/ai-sdk";
import { DakeraClient } from "@dakera-ai/dakera";

const client = new DakeraClient({ baseUrl: process.env.DAKERA_URL! });
const agentId = "user-1234";

const model = wrapLanguageModel({
  model: openai("gpt-4o"),
  middleware: createDakeraMemoryMiddleware({ client, agentId, store: false }),
});
const tools = createDakeraTools({ client, agentId });

// Middleware recalls context; the model writes back only what matters
await generateText({ model, tools, maxSteps: 6, prompt: "My deadline for Velox is Friday the 11th." });

Sharing a pre-built client

Pass a DakeraClient via the client option to reuse one connection across many agents — or to avoid repeating credentials. When client is set, apiUrl / apiKey are ignored and the client is used directly, on both the middleware and the tools.

const client = new DakeraClient({
  baseUrl: "http://my-dakera.internal:3000",
  apiKey: process.env.DAKERA_API_KEY,
});

// Every agent shares the same connection, scoped by its own agentId
const supportMiddleware = createDakeraMemoryMiddleware({ client, agentId: "support-bot" });
const salesTools = createDakeraTools({ client, agentId: "sales-bot" });

Error safety

Persisting memory must never break generation. In wrapGenerate, the model runs first via doGenerate(); only afterwards does the middleware attempt to write the exchange, and that write is wrapped in a try / catch that swallows storage errors. A memory-server outage degrades a call to a plain, stateless generation — never a crash or a 500.

// Simplified from src/middleware.ts — write-back is best-effort
const result = await doGenerate();
if (!shouldStore) return result;
try {
  // store "User: …" and "Assistant: …" at the configured importance
  await client.storeMemory(agentId, { content, importance });
} catch {
  // Swallow storage errors: memory is best-effort, generation is not.
}
return result;
Recall is defensive too. Calls with no user text, or an empty recall result, pass the prompt through unchanged — so an empty memory store on day one simply behaves like a normal, stateless model.

Options

Middleware — createDakeraMemoryMiddleware

OptionTypeDefaultDescription
agentIdstringScopes stored/recalled memories to a user or agent (required)
recallKnumber5Memories recalled and injected per call (top_k)
minImportancenumber0Minimum importance threshold for recall (min_importance)
importancenumber0.7Importance assigned to memories stored after each call
storebooleantrueWhether to write the exchange back after generation
headerstringsee noteHeader line prepended to the injected memory block
clientDakeraClientPre-built client; overrides apiUrl/apiKey
apiUrlstring$DAKERA_URL / http://localhost:3000Dakera server base URL
apiKeystring$DAKERA_API_KEYDakera API key (dk-…)

Default header: "Relevant memories from previous sessions (use them if helpful):"

Tools — createDakeraTools

OptionTypeDefaultDescription
agentIdstringScopes stored/recalled memories to a user or agent (required)
recallKnumber5Default number of memories returned by recallMemory
importancenumber0.7Default importance for storeMemory when the model omits it
clientDakeraClientPre-built client; overrides apiUrl/apiKey
apiUrlstring$DAKERA_URL / http://localhost:3000Dakera server base URL
apiKeystring$DAKERA_API_KEYDakera API key (dk-…)

Tool call inputs

ToolInputTypeNotes
recallMemoryquerystringWhat to search for in memory (required)
recallMemorytopKnumberPositive integer; overrides recallK for this call (optional)
storeMemorycontentstringThe fact to remember (required)
storeMemoryimportancenumberWeight from 0 (trivial) to 1 (critical); overrides the default (optional)
Two extension points, zero lock-in. Middleware makes memory transparent to your app; tools give the model explicit control. Use either or both — your provider and model code stay unchanged.

Environment variables

Connection resolves in this order: an explicit client, then apiUrl/apiKey options, then these environment variables, then the local default URL.

VariableDefaultDescription
DAKERA_URLhttp://localhost:3000Dakera server base URL (can be omitted)
DAKERA_API_KEYAPI key for the Dakera server (dk-…)

Next.js & edge

The integration is plain server-side TypeScript, so it drops into Next.js Route Handlers, Server Actions, and server components. Read DAKERA_URL / DAKERA_API_KEY from the server environment and keep the wrapped model (or tools) on the server — never ship your API key to the browser.

// app/api/chat/route.ts
import { streamText, wrapLanguageModel } from "ai";
import { openai } from "@ai-sdk/openai";
import { createDakeraMemoryMiddleware } from "@dakera-ai/ai-sdk";

export async function POST(req: Request) {
  const { messages, userId } = await req.json();
  const model = wrapLanguageModel({
    model: openai("gpt-4o-mini"),
    middleware: createDakeraMemoryMiddleware({ agentId: userId }),
  });
  const result = streamText({ model, messages });
  return result.toUIMessageStreamResponse();
}

Related integrations

Links

Troubleshooting

SymptomFix
Recall is empty on the first callExpected — the first call has no history. After the first exchange stores, later calls recall.
A Dakera outage breaks generationIt won't. wrapGenerate catches all storage errors, so an outage degrades to a stateless call, never a crash.
Streamed turns aren't being rememberedWrite-back runs in wrapGenerate (generate calls). For streamText, add the tools pattern or call storeMemory explicitly.
Recalled memories look stale or off-topicRaise minImportance to filter low-importance recall, or lower recallK so fewer, more relevant memories are injected.
Server returns 401Verify DAKERA_API_KEY matches the server's key. Keys look like dk-….
Cannot find name 'process'Add "types": ["node"] to your tsconfig.json compilerOptions.
Peer dependency warnings on installEnsure ai ≥ 6.0.0, @dakera-ai/dakera ≥ 0.11.54, and zod ≥ 3.23.0 are installed alongside the package.

Frequently Asked Questions

How do I add memory to the Vercel AI SDK?

Install @dakera-ai/ai-sdk, run a self-hosted Dakera server, then wrap your model with createDakeraMemoryMiddleware or add createDakeraTools to your generateText / streamText call. No model or provider changes required.

Does Dakera work with streaming?

Yes. Recall injection runs in transformParams, which the SDK applies to both generateText and streamText, so streamed calls recall context transparently. Automatic write-back runs in wrapGenerate (generate calls); to persist streamed turns, use the tools pattern or call storeMemory explicitly.

What happens if the Dakera server is down?

Generation still succeeds. The middleware runs the model first, then attempts write-back inside a try / catch that swallows storage errors — so an outage degrades to a plain stateless call instead of an exception.

What is the difference between the middleware and the tools?

The middleware recalls and stores transparently on every call with no prompt changes. The tools (recallMemory / storeMemory) hand control to the model, so it decides when to look something up or persist a fact. They compose — run the middleware with store: false for automatic recall and let the tools own writes.

What does Dakera add to the AI SDK?

Persistent cross-session memory with decay-weighted recall — importance-scored memories that decay over time, so stale context stops competing with fresh, relevant facts. The same engine scores 88.2% Recall@20 on the 1,540-question LoCoMo benchmark.

Ready to ship?

Deploy Dakera in minutes — self-hosted and free. Cloud managed hosting coming soon.

Self-Host Free → Join Cloud Waitlist →

Give your AI agents persistent memory

Self-host free today — or join the Dakera Cloud waitlist for managed hosting, SLA & founder pricing.

✓ You're on the list. We'll be in touch.