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) β
βββββββββββββββ βββββββββββββββ βββββββββββββββββββ
β
βΌ
βββββββββββββββ
β SQLite β
β (storage) β
βββββββββββββββ
curl -fsSL https://download.tapes.dev/install | bash# In terminal 1: Start Tapes server
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 |
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',
},
});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 a SQLite database at ~/.tapes/tapes.sqlite.
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