Skip to content
telemetry.dev
Esc
↑↓navigate↵open⌘Jpreview
On this page

Eve

Export Eve agent turns, model spans, lifecycle logs, token usage, time to first token, and errors.

@telemetry-dev/eve connects Vercel’s Eve agent framework to telemetry.dev. Add it to your Eve server to export each agent turn with its model calls, tool calls, token usage, and lifecycle logs. Add it to an Eve HTTP client to record each turn from the caller’s side.

Install

npm install @telemetry-dev/eve eve
pnpm add @telemetry-dev/eve eve
yarn add @telemetry-dev/eve eve
bun add @telemetry-dev/eve eve

The package supports eve 0.47.0 and later 0.x releases, and needs Node.js 24 or later.

Set the API key

Create a project at telemetry.dev and copy an API key from the project setup page. Set it in the Eve server process:

export TELEMETRY_DEV_API_KEY=td_live_...
export TELEMETRY_DEV_ENVIRONMENT=development
export OTEL_SERVICE_NAME=eve-agent

Without an API key, the integration does nothing.

Choose what to add

The package has three entry points. Use any combination:

Entry point Where it runs What it records
telemetryDevInstrumentation() Eve server, agent/instrumentation.ts Agent turn spans and model and tool spans from Eve’s OpenTelemetry
telemetryDevHook() Eve server, agent/hooks/ Lifecycle logs for sessions, turns, steps, tools, approvals, and subagents
wrapEveClient() Your app that calls Eve over HTTP One invoke_agent span per turn, from the caller’s side

Pass SDK options to one entry point per process. The first entry point that starts sets the options for the process, and later calls ignore new options.

Add server instrumentation

Create agent/instrumentation.ts:

import "dotenv/config";
import { telemetryDevInstrumentation } from "@telemetry-dev/eve";

export default telemetryDevInstrumentation();

The eve dev server does not load your app’s .env file, so the example imports dotenv/config to load it. Leave that line out if you set the variables another way.

This registers telemetry.dev as the global OpenTelemetry tracer provider when Eve starts, so spans from Eve and from the AI SDK inside it export to telemetry.dev. The integration also adds user.id when Eve session authentication has a principal.

The service name comes from the serviceName option, then OTEL_SERVICE_NAME, then the Eve agent name.

To add your own context to model-call spans, pass runtimeContext or a stepStarted callback:

import { telemetryDevInstrumentation } from "@telemetry-dev/eve";

export default telemetryDevInstrumentation({
  functionId: "support-agent",
  runtimeContext: { "team.id": "support" },
  stepStarted(input) {
    return { runtimeContext: { "channel.kind": input.channel.kind } };
  },
});

Eve records runtime context on model-call spans under the ai.settings.context. prefix.

Add lifecycle logs

Create agent/hooks/telemetry-dev.ts:

import { telemetryDevHook } from "@telemetry-dev/eve";

export default telemetryDevHook();

The hook writes a log for each of these Eve events:

  • Session started, waiting, completed, and failed
  • Turn started, completed, cancelled, and failed
  • User message received (without the message content)
  • Step started, completed (with usage when available), and failed
  • Tool results that did not complete
  • Input requests and authorization outcomes
  • Subagent calls and completions
  • Compaction requests and completions

Each log carries gen_ai.conversation.id, gen_ai.agent.name, the Eve turn and step IDs when present, and an eventName equal to the Eve event type. Logs from one session share its conversation ID, so they line up with the session’s spans.

Wrap an Eve client

Use wrapEveClient() in an app or script that calls an Eve agent over HTTP:

import { flush } from "@telemetry-dev/sdk";
import { wrapEveClient } from "@telemetry-dev/eve";
import { Client } from "eve/client";

const client = wrapEveClient(new Client({ host: "http://127.0.0.1:2000" }), {
  agentName: "support-agent",
  serviceName: "support-agent-client",
  onError: (error) => console.error("[telemetry.dev]", error),
});

const { response, session } = await client.sessions.create({
  message: "Summarize this incident.",
});
console.log(await response.result());

const followUp = await session.send("What do we do next?");
console.log(await followUp.result());

await flush();

The example imports flush from @telemetry-dev/sdk. Install it next to the integration so your code can import it:

npm install @telemetry-dev/sdk
pnpm add @telemetry-dev/sdk
yarn add @telemetry-dev/sdk
bun add @telemetry-dev/sdk

Call flush() before a script exits; the SDK does not flush on exit by itself. In serverless code, pass waitUntil or set exportMode: "immediate" instead.

The wrapper makes one caller-side invoke_agent span for each turn started by sessions.create(), ClientSession.send(), or ClientSession.respond(). Sessions returned by sessions.attach() are wrapped too, so their send() and respond() turns are traced. Each span records:

  • The outgoing message, or the input responses for respond()
  • Time to first token, from the first message or reasoning delta
  • Input, output, cache-read, and cache-write token usage, summed from step.completed events
  • The output from the final message or result event
  • Cancellations, incomplete streams, and Eve failures, as errors
  • Subagent calls, input requests, and authorization requests, as span events

gen_ai.conversation.id comes from the Eve session, so all turns of one session share one trace.

Read the response to the end, or abort it, to close the span. A response you never read keeps its span open, and it is never exported.

Check the trace

Start the Eve server, run one turn, and open Traces in your project. A turn traced by the server instrumentation contains:

  • An Eve root span named ai.eve.turn, which telemetry.dev shows as invoke_agent
  • AI SDK spans under it: chat {modelId}, step N, provider chat {modelId} spans, and execute_tool {toolName} spans
  • Token usage, finish reasons, response IDs, and time to first token when Eve’s AI SDK integration reports them

All turns of one Eve session land in one trace.

Control captured content

Whether model content reaches telemetry.dev depends on Eve’s trace policy as well as on this integration.

telemetryDevInstrumentation() maps captureInput and captureOutput to Eve’s recordInputs and recordOutputs. Both default to true, but they cannot override Eve’s trace policy. In Eve 0.47.0 and 0.50.0, the default policy records content only for a public audience, even with EVE_DEV=1. HTTP clients, web chat, and schedules have an unknown audience, so their content is not recorded.

To record content for these channels during local development, switch to Eve’s provider layout:

Remove the single-file instrumentation

Delete agent/instrumentation.ts. The two layouts cannot be combined: with providers turned on, Eve treats an agent/instrumentation.ts file as a build error.

Turn on instrumentation providers

In agent.ts, set experimental.instrumentationProviders to true.

Add the OpenTelemetry provider

import { otel } from "eve/instrumentation/otel";

export default otel({ tracePolicy: () => true });

Add telemetry.dev as a destination

import { telemetryDevOtelIntegration } from "@telemetry-dev/eve";

export default telemetryDevOtelIntegration();

With this policy, Eve applies its own audience rules. For local unknown-audience channels, run eve dev or set EVE_DEV=1 in the Eve server process; without it, those channels export metadata only. Local private channels always export metadata only. Destination settings and forwarded parent policies can reduce capture further. This package cannot restore content that Eve removed.

In the provider layout, Eve owns the tracer provider, so telemetryDevOtelIntegration() adds a span processor instead of registering a provider. It accepts the span processor options plus recordInputs and recordOutputs. Eve 0.50 gives it no way to set the parent context, so provider-layout spans do not join the per-session trace.

For the client wrapper, content follows the SDK captureInput and captureOutput options. The SDK mask and maxAttributeLength options apply to its captured inputs, outputs, and logs.

Which spans are exported

By default, the integration exports only AI spans: the tracer scopes eve, eve.agent, and gen_ai, and the client wrapper’s own spans. Spans from other libraries that use the same global tracer provider, such as better-auth, Nitro, or Eve’s workflow engine, are dropped.

To export every span, pass spanFilter: () => true. To export a different set, pass your own spanFilter.

Options

Every entry point accepts the init() options of @telemetry-dev/sdk, except registerGlobal, which the integration always sets to true. The variables you are most likely to set:

Variable Default Description
TELEMETRY_DEV_API_KEY None Ingest API key. Without a key, the integration does nothing.
TELEMETRY_DEV_BASE_URL https://ingest.telemetry.dev Ingest base URL
TELEMETRY_DEV_ENVIRONMENT production Environment on exported telemetry
OTEL_SERVICE_NAME See below Service name

The server instrumentation falls back to the Eve agent name for the service name. The hook and client wrapper do not, so set serviceName or OTEL_SERVICE_NAME when you use them alone.

telemetryDevInstrumentation() also accepts:

Option Type Default Description
functionId string Eve agent name Eve’s telemetry function ID
recordInputs boolean captureInput, then true Record model inputs, subject to Eve’s trace policy
recordOutputs boolean captureOutput, then true Record model outputs, subject to Eve’s trace policy
runtimeContext Eve InstrumentationRuntimeContext None Static context added to each model-call span
stepStarted Eve step.started callback None Adds context when a step starts. Its values win on key conflicts.

wrapEveClient() also accepts:

Option Type Default Description
agentName string None Value for gen_ai.agent.name
spanName string invoke_agent Name of each turn span

Errors inside the integration go to onError and are never thrown into Eve.

Limitations

  • The client wrapper does not trace ClientSession.stream(), Client.info(), or Client.health(). Attached streams have no end, and the probe routes are not agent turns.
  • The client wrapper reports a total cost only after a turn ends without error and every step reports a finite, non-negative usage.costUsd, including zero. Otherwise it records token usage without a total cost. Server-side pricing also needs the model name.
  • When caller and server spans share a trace and match on Eve turn metadata, the caller’s cost replaces the cost of the server’s main model calls. Tool and subagent costs stay separate. Without a match, both totals stay as reported.
  • The hook does not forward every event of an inline subagent’s stream. It logs subagent started, called, and completed events, and child failures.
  • Eve workflow $eve.* run tags appear only in the Vercel dashboard. They are not visible to OpenTelemetry, so this package cannot capture them.

For the package source and changelog, see @telemetry-dev/eve on GitHub.

Last updated on October 18, 2026

Was this page helpful?