Vercel AI SDK
Trace an AI SDK v7 agent in Harbor with two lines of setup.
AI SDK v7 emits OpenTelemetry spans in the GenAI conventions Harbor reads, so nothing translates between them. Register Harbor's tracer provider, hand the AI SDK its OpenTelemetry integration, and every call is traced.
Install
npm install harbor-ai-sdk @ai-sdk/otel@ai-sdk/otel is the AI SDK's own OpenTelemetry integration; it moved out of the ai
package in v7.
Set up
import { OpenTelemetry } from '@ai-sdk/otel'
import { registerTelemetry } from 'ai'
import { Harbor } from 'harbor-ai-sdk'
export const harbor = new Harbor()
registerTelemetry(new OpenTelemetry())import { harbor } from './instrumentation.ts'
import { assistant } from './agent.ts'
const result = await assistant.generate({
prompt: 'What is the weather in Barcelona?',
telemetry: { functionId: 'assistant' },
})
// Batched spans would die with a short-lived process.
await harbor.flush()That is the whole integration. Registering an integration makes telemetry opt-out: every
AI SDK call is traced, with no per-call flag. functionId becomes gen_ai.agent.name, which
is what names the agent in Harbor.
What you get
One agent run arrives as a nested trace:
invoke_agent {model} the run
step 1 one iteration of the tool loop
chat {model} a model call
execute_tool {name} a tool the model asked for
step 2
chat {model}Model spans carry provider, request and response model, input and output messages, token usage — including cache-read and cache-creation — and finish reasons. Tool spans carry the tool name, call id, arguments and result.
Add the user and session
The AI SDK has no notion of who is asking, so capture supplies it. Every span underneath
inherits both, including the spans the AI SDK created.
import { capture } from 'harbor-ai-sdk'
const result = await capture(
'assistant-turn',
() => assistant.generate({ prompt, telemetry: { functionId: 'assistant' } }),
{ sessionId: conversation.id, userId: user.id },
)Reuse sessionId across turns and Harbor stitches them into one session. capture also
takes tags and metadata — see the TypeScript SDK
page.
Setup placement
registerTelemetry writes to a global the AI SDK reads on each call, and nothing is
monkey-patched. So it only has to run before your first generate — not before any import,
and not in a particular module.
That also means a preload module works, with no tracing code in your application at all:
node --import ./dist/instrumentation.js dist/index.jsA preload flushes only on beforeExit, which does not fire on process.exit() or a
signal. An explicit await harbor.flush() is more reliable for scripts and handlers.
@ai-sdk/otel also exports LegacyOpenTelemetry, which emits the pre-v7 attribute names.
Harbor reads the current GenAI conventions, so use OpenTelemetry. On AI SDK v6 there is
no integration to register — enable telemetry per call with
experimental_telemetry: { isEnabled: true } instead.