Harbor
Monitoring

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

src/instrumentation.ts
import { OpenTelemetry } from '@ai-sdk/otel'
import { registerTelemetry } from 'ai'
import { Harbor } from 'harbor-ai-sdk'

export const harbor = new Harbor()

registerTelemetry(new OpenTelemetry())
src/index.ts
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.js

A 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.

On this page