> ## Documentation Index
> Fetch the complete documentation index at: https://docs.maximem.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Vercel AI SDK

> Model middleware that wraps any Vercel AI SDK model with automatic Synap context.

Wrap any Vercel AI SDK model with one line and get persistent, per-user memory on every call. `generateText`, `streamText`, and `generateObject` all keep working; the middleware just makes them memory-aware.

<Warning>
  `@maximem/synap-vercel-adk` wraps the JavaScript SDK, which in turn wraps the Python SDK as a subprocess. **It requires a Python 3.11+ runtime on the host.** Edge Runtime, Cloudflare Workers, Bun, Deno Deploy, and AWS Lambda Node-only runtimes are not supported. Pin Next.js route handlers to `export const runtime = "nodejs"`. See [Installation → JavaScript / TypeScript SDK](/setup/installation#javascript-typescript-sdk).
</Warning>

<Note>
  Runtime: Node.js 18+ only. The JS SDK wraps the Python SDK as a subprocess, so it needs Python 3.11+ on the host and does NOT run on Edge / Cloudflare Workers / Bun / Deno / Lambda-Node-only. On Next.js, pin the route to runtime = "nodejs".
</Note>

## Overview

This guide shows how to add Synap to a Vercel AI SDK application to build apps that:

* Inject relevant memory before every `generateText` / `streamText` / `generateObject` call
* Record completed turns back to Synap automatically
* Work with any provider (OpenAI, Anthropic, Google, etc.) without per-provider plumbing

The Synap Vercel AI SDK integration ships a factory plus a provider class.

| Export          | Purpose                                           |
| --------------- | ------------------------------------------------- |
| `createSynap`   | Async factory that initializes the Synap provider |
| `SynapProvider` | Provider class with `wrap` and `listen` methods   |

## Setup

Install the package:

```bash theme={null}
npm install @maximem/synap-vercel-adk ai @ai-sdk/openai
```

<Note>
  Import the integration from its package name directly: `import { createSynap } from "@maximem/synap-vercel-adk"`. `createSynap` initializes the underlying Synap SDK for you, so you don't import the core `@maximem/synap-js-sdk` package separately.
</Note>

Configure your API key. Generate one from the [Synap Dashboard](https://synap.maximem.ai).

```bash .env theme={null}
SYNAP_API_KEY=synap_your_key_here
OPENAI_API_KEY=your-openai-api-key
```

Initialize the provider once at application startup:

```typescript theme={null}
import { createSynap } from "@maximem/synap-vercel-adk";

const synap = await createSynap({
  apiKey: process.env.SYNAP_API_KEY!,
});
```

`createSynap` initializes the underlying Synap SDK internally, so you don't need to manage the SDK lifecycle separately. See [SDK Initialization](/sdk/initialization) for the full lifecycle if you'd rather construct the SDK directly.

## Basic integration

The smallest useful integration wraps a model with `synap.wrap` and uses it like any other Vercel AI SDK model:

```typescript theme={null}
// npm install @maximem/synap-vercel-adk ai @ai-sdk/anthropic
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { createSynap } from "@maximem/synap-vercel-adk";

// createSynap initializes the underlying Synap SDK for you
const synap = await createSynap({ apiKey: process.env.SYNAP_API_KEY! });

const model = synap.wrap(anthropic("claude-sonnet-4-6"), {
  userId: "alice",
  customerId: "acme",   // optional, required for B2B instances
});

const { text } = await generateText({
  model,
  messages: [{ role: "user", content: "What do you remember about my account?" }],
});
```

On every call, the middleware fetches Synap context, injects it as a system message, proxies the request to the wrapped model, and ingests the resulting turn back into Synap. **Context-fetch failures degrade gracefully** (empty context, error logged); **turn-ingestion failures surface explicitly** so silent data loss is impossible.

***

## Core concepts

### synap.wrap

`synap.wrap(model, options)` returns a standard Vercel AI SDK `LanguageModel`. Drop it anywhere you'd use the underlying model: `generateText`, `streamText`, `generateObject`, agentic loops, structured output, etc.

```typescript theme={null}
const model = synap.wrap(openai("gpt-4o"), {
  userId: "alice",
  customerId: "acme",
  conversationId: crypto.randomUUID(),  // optional, biases retrieval to this thread; must be a valid UUID
});
```

The scoping triple is bound when you call `wrap`. The model only ever sees the messages, never `userId`/`customerId`. This means prompt injection cannot spoof scope.

### The middleware loop

On every call to the wrapped model:

```text theme={null}
your code → synap.wrap(model) → [fetch context] → wrapped model → [ingest turn] → your code
```

1. **Before**: fetches the user's Synap context and injects it as a system message.
2. **Generates**: proxies the request to the wrapped model unchanged.
3. **After**: ingests the completed user + assistant turn into Synap asynchronously.

Steps 1 and 3 are independent: a failure in either does not block the other.

### Provider-agnostic

Wrap any Vercel AI SDK-compatible model:

```typescript theme={null}
import { openai } from "@ai-sdk/openai";
import { google } from "@ai-sdk/google";
import { anthropic } from "@ai-sdk/anthropic";

const gptWithMemory    = synap.wrap(openai("gpt-4o"),                  { userId: "alice" });
const geminiWithMemory = synap.wrap(google("gemini-2.0-flash"),         { userId: "alice" });
const claudeWithMemory = synap.wrap(anthropic("claude-sonnet-4-6"),     { userId: "alice" });
```

The middleware operates on the Vercel AI SDK abstraction, so the same wrap behavior applies to every provider.

### Streaming

`streamText` and `streamObject` work without any code changes; the middleware injects context before the stream starts and ingests the turn when the stream ends:

```typescript theme={null}
import { streamText } from "ai";

const { textStream } = await streamText({
  model: synap.wrap(openai("gpt-4o"), { userId: "alice" }),
  messages: [{ role: "user", content: "Summarize my recent priorities." }],
});

for await (const chunk of textStream) {
  process.stdout.write(chunk);
}
```

***

## Complete example: chat route with per-request scoping

The pattern below is a typical Next.js (Node runtime) chat route. Each request gets its own scope baked into a freshly-wrapped model, so multiple concurrent users cannot leak into each other's memory:

```typescript theme={null}
// app/api/chat/route.ts
export const runtime = "nodejs";   // required, see runtime warning above

import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { createSynap } from "@maximem/synap-vercel-adk";

// Initialize once at module load
const synap = await createSynap({ apiKey: process.env.SYNAP_API_KEY! });

export async function POST(req: Request) {
  const { userId, customerId, messages } = await req.json();

  const model = synap.wrap(openai("gpt-4o"), { userId, customerId });

  const result = await streamText({ model, messages });
  return result.toDataStreamResponse();
}
```

Three things to notice in this pattern:

1. **The provider is module-scoped, the model is request-scoped.** `createSynap` runs once; `synap.wrap` runs per request with the right `userId`.
2. **Streaming is unchanged.** No special integration needed; `streamText` and the middleware co-exist transparently.
3. **The `runtime = "nodejs"` pin matters.** Edge Runtime would break the Python-subprocess dependency.

***

## Advanced patterns

### Per-request scoping

For multi-tenant services, build the wrapped model per request and never cache it across users:

```typescript theme={null}
async function handleChat(userId: string, message: string) {
  const model = synap.wrap(openai("gpt-4o"), { userId });
  const { text } = await generateText({
    model,
    messages: [{ role: "user", content: message }],
  });
  return text;
}
```

### Anticipation stream

`synap.listen()` opens a gRPC stream that pre-fetches context speculatively before the user's next request arrives. This reduces perceived latency in long-lived server processes where you can predict who will message next:

```typescript theme={null}
const stop = synap.listen({ userId: "alice" });

// Later, when the session ends:
stop();
```

Use this for active sessions where you've already identified the user (e.g. after authentication on an open WebSocket) and want to warm up retrieval ahead of their first message.

### Multi-tenant scoping

`synap.wrap` accepts the standard scoping triple: `userId` (required), optional `customerId`, optional `conversationId`. `customerId` is required on B2B Synap instances and ignored on single-tenant ones. See [Memory Scopes](/concepts/memory-scopes).

```typescript theme={null}
const model = synap.wrap(openai("gpt-4o"), {
  userId: "alice",
  customerId: "acme",
});
```

### Failure semantics

The middleware follows the Synap-wide contract:

* **Context fetch degrades gracefully**: empty context is injected and the error logged if Synap is unreachable.
* **Turn ingestion surfaces failures**: write failures raise `SynapIntegrationError` so callers know if persistence failed.

This is by design: read failures shouldn't break a user-facing turn, but silent write failures would let memory drift away from reality.

***

## Going further

* [Patterns overview](/patterns/overview): reusable memory patterns across frameworks.
* [Cookbook overview](/cookbook/overview): end-to-end worked examples.

***

## Next steps

<CardGroup cols={2}>
  <Card title="Mastra" icon="layer-group" href="/integrations/mastra">
    `SynapMemory` and tools for Mastra.
  </Card>

  <Card title="Claude Agent SDK" icon="wand-magic-sparkles" href="/integrations/claude-agent">
    Hooks and MCP server for the Claude Agent SDK.
  </Card>

  <Card title="Context Fetch" icon="search" href="/sdk/context-fetch">
    The retrieval API behind `synap.wrap`: modes, scopes, and response shapes.
  </Card>

  <Card title="Memory Scopes" icon="layer-group" href="/concepts/memory-scopes">
    How `userId`, `customerId`, and `conversationId` interact across reads.
  </Card>
</CardGroup>
