A proof-of-concept showing how to integrate Tapes with the Vercel AI SDK using a custom fetch wrapper.
This integration routes AI SDK requests through a local Tapes proxy for:
- 📼 Recording all LLM requests/responses
- 🔍 Searchable conversation history
- 🔄 Replay and checkpointing of agent sessions
- 📊 Observability without modifying the AI SDK
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
│ AI SDK │ ───► │ Tapes Proxy │ ───► │ LLM Provider │
│ (your app) │ │ (localhost) │ │ (OpenAI/Claude) │
└─────────────┘ └─────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ PostgreSQL / │
│ SQLite (storage)│
└─────────────────┘
curl -fsSL https://download.tapes.dev/install | bash# Recommended: Start Tapes with PostgreSQL storage
tapes serve --postgres "postgres://user:pass@localhost:5432/tapes"
# Or use the default SQLite storage
tapes servecd tapes-ai-sdk-example
npm install# For OpenAI
export OPENAI_API_KEY=sk-...
# Or for Anthropic
export ANTHROPIC_API_KEY=sk-ant-...
export PROVIDER=anthropic# Smoke test + start server (runs examples, then chat, then web UI)
npm start
# Or run individually:
npm run dev # examples only
npm run chat # interactive CLI chat
npm run server # web UI onlyThe tapes/ai.js module centralizes all proxy and provider configuration:
// Pre-configured singletons (reads env vars at import time)
import { model, config } from './tapes/ai.js';
const { text } = await generateText({
model,
prompt: 'Hello, world!',
});For server use where each request needs its own session tracking:
import { createSessionModel } from './tapes/ai.js';
const model = createSessionModel('user-123-chat');import { createTapesProvider } from './tapes/ai.js';
const { provider, model } = createTapesProvider({
sessionId: 'my-session',
provider: 'anthropic',
model: 'claude-sonnet-4-5-20250929',
debug: true,
});For direct control, use tapes-fetch.js directly:
import { createTapesFetch } from './tapes-fetch.js';
import { createOpenAI } from '@ai-sdk/openai';
const tapesFetch = createTapesFetch({
proxyUrl: 'http://localhost:8080',
headers: { 'X-Tapes-Session': 'my-session-id' },
});
const openai = createOpenAI({
fetch: tapesFetch,
apiKey: process.env.OPENAI_API_KEY,
});The npm run server command launches an Express server with a browser-based chat interface at http://localhost:3000. It uses streamText to stream responses in real-time through the Tapes proxy and renders them in a dark-themed chat UI with session tracking and conversation clearing.
| Variable | Default | Description |
|---|---|---|
TAPES_PROXY_URL |
http://localhost:8080 |
Tapes proxy address |
PROVIDER |
openai |
LLM provider (openai or anthropic) |
MODEL |
gpt-4o-mini / claude-sonnet-4-5-20250929 |
Model to use |
PORT |
3000 |
Web UI server port |
DEBUG |
false |
Enable debug logging |
TAPES_RETRY_ATTEMPTS |
3 |
Max retry attempts for proxy requests |
TAPES_FAILOVER |
false |
Failover to direct provider URL on proxy failure |
TAPES_POSTGRES_DSN |
- | PostgreSQL connection string (e.g. postgres://user:pass@localhost:5432/tapes) |
OPENAI_API_KEY |
- | OpenAI API key |
ANTHROPIC_API_KEY |
- | Anthropic API key |
Requests through the Tapes proxy automatically retry on network errors (ECONNREFUSED, ECONNRESET, ETIMEDOUT) and HTTP 502/503/504 responses using exponential backoff.
When failover is enabled and all proxy retries are exhausted, one final attempt is made directly to the LLM provider, bypassing the proxy. This keeps your app functional even when the Tapes proxy is down.
const { model } = createTapesProvider({
retry: { maxAttempts: 5, initialDelayMs: 1000, maxDelayMs: 10000 },
failover: true,
});| Option | Default | Description |
|---|---|---|
retry.maxAttempts |
3 |
Total attempts before giving up |
retry.initialDelayMs |
500 |
First retry delay (doubles each attempt) |
retry.maxDelayMs |
5000 |
Maximum delay between retries |
failover |
false |
Bypass proxy as a last resort |
Add metadata to your requests for better organization:
const tapesFetch = createTapesFetch({
proxyUrl: 'http://localhost:8080',
headers: {
'X-Tapes-Session': 'user-123-chat',
'X-Tapes-App': 'my-chatbot',
'X-Tapes-Environment': 'production',
},
});Tapes supports three storage backends:
| Backend | Flag | Best for |
|---|---|---|
| PostgreSQL | --postgres "dsn" |
Production, team environments, persistent storage |
| SQLite | (default) | Local development, single-user setups |
| In-memory | --memory |
Testing, ephemeral sessions |
PostgreSQL is the recommended default for anything beyond local experimentation. Pass the DSN via the CLI flag or set TAPES_POSTGRES_DSN in your environment:
# Via CLI flag
tapes serve --postgres "postgres://user:pass@localhost:5432/tapes"
# Or via env var
export TAPES_POSTGRES_DSN="postgres://user:pass@localhost:5432/tapes"
tapes serve --postgres "$TAPES_POSTGRES_DSN"For remote providers, team setup, and managed Postgres options, see the PostgreSQL Storage guide on tapes.dev.
After running conversations through Tapes:
# Search conversations
tapes search "your query"
# View recent activity
tapes log
# Checkout a previous state
tapes checkout <hash>Tapes stores all recorded data in either PostgreSQL or SQLite (at ~/.tapes/tapes.sqlite for local). The schema is the same for both backends.
The primary table — each row is a message node in a conversation tree:
| Column | Type | Description |
|---|---|---|
hash |
text (PK) | Content-addressable identifier |
parent_hash |
text (FK → nodes) | Links to parent message, forming conversation trees |
role |
text | user, assistant, or system |
content |
json | Full message JSON with metadata |
model |
text | e.g. gpt-4o-mini, claude-sonnet-4-5-20250929 |
provider |
text | e.g. openai, anthropic |
prompt_tokens |
integer | Input token count |
completion_tokens |
integer | Output token count |
total_tokens |
integer | Combined token count |
total_duration_ns |
integer | Total request duration in nanoseconds |
prompt_duration_ns |
integer | Time-to-first-token in nanoseconds |
stop_reason |
text | e.g. end_turn, max_tokens |
bucket |
json | Grouping/tagging metadata |
type |
text | Node type classifier |
created_at |
datetime | Timestamp (defaults to current time) |
Indexed on parent_hash, role, model, provider, and (role, model).
Messages form a directed acyclic graph (DAG) via parent_hash references:
- Root nodes (
parent_hashis null) are conversation starts - Branching — multiple children can share a parent, enabling conversation forks
- Checkpointing —
tapes checkout <hash>restores any previous state - Replay — walk the DAG to replay or inspect conversation history
The vec_embeddings table stores 768-dimensional float vectors for semantic search, linked to nodes via the vec_documents table. Query with tapes search "your query".
tapes/ai.js- Pre-configured provider wrapper (main entry point)tapes-fetch.js- Low-level custom fetch wrapper for Tapes proxyindex.js- Example usage (generateText, streamText, multi-turn conversation)chat.js- Interactive CLI chatserver.js- Express web server with chat UIpublic/index.html- Web chat interface
MIT