---
title: Vercel AI SDK
description: Wire AgentPing into generateText and streamText through onFinish so every AI SDK call lands with usage, cost, and the tools it used.
section: frameworks
order: 3
---

# Vercel AI SDK

The Vercel AI SDK (`ai` on npm) reports usage through the `onFinish` callback on `generateText` and `streamText`, and per-step detail through `onStepFinish`. The TypeScript SDK ships a helper that builds those callbacks for you, so one AI SDK call lands as one `llm_call` plus a `tool_call` for each tool the model used. TypeScript only; there is no Python AI SDK.

## Install / enable

```bash
npm install @agentping/sdk ai
```

```typescript
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import * as agentping from "@agentping/sdk";

agentping.init({ apiKey: process.env.AGENTPING_API_KEY });

const run = agentping.run("support-triage", { customerId: "acme-corp" });

const { text } = await generateText({
  model: openai("gpt-4o-mini"),
  prompt: "classify this ticket",
  tools: { lookupOrder },
  ...agentping.withAgentPing(run),
});

await run.finish({ status: "success" });
```

`withAgentPing(run?, options?)` returns `{ onFinish, onStepFinish? }`, so spread it into the call. If you already have an `onFinish`, take just the handler with `agentPingOnFinish(run?, options?)` and call it from yours. Options: `provider` and `model` overrides, `perStep`, `captureToolPayloads` (default true) and `toolPayloadMaxChars` (default 4000). The run argument is optional; without it the handler uses the active run from `runScopeAsync`.

## Events

| AI SDK callback | AgentPing event | What is recorded |
|---|---|---|
| `onFinish` | `llm_call` | Provider and model from `response.modelId` (`openai/gpt-4o-mini` splits into the two), input and output tokens summed across all steps of the call (`totalUsage`), cached and reasoning tokens when reported, finish reason, number of tool calls, latency for the whole call. |
| `onFinish` | `tool_call`, one per tool the model used | Tool name, `tool_invocation_id` (the AI SDK's `toolCallId`), arguments and result (capped at `toolPayloadMaxChars`). A tool whose step reports a `tool-error` part lands with `status: "error"` and the error message instead of a result. |
| `onStepFinish` with `perStep: true` | `llm_call` per model step | The same fields per step, so a tool-using call shows each round trip separately, each followed by its own `tool_call` rows. `onFinish` then emits nothing extra. |

By default a call that loops through several tool steps is one `llm_call` with the summed usage, which is what the rate card prices. Turn on `perStep` when you want to see the cost of each round trip.

Three gaps to know. A call that throws before `onFinish` (a rejected request, a rate limit) never reaches it, so there is no errored `llm_call`; record the failure with `run.finish({ status: "error" })` or your own `error` event. `streamText` is captured through the same `onFinish` once the stream completes, but the `llm_call` carries no `stream` flag. And `tool_call` rows carry no latency, because the AI SDK runs tools itself and does not report per-tool timing.

## Naming

Provider and model come from the AI SDK's `response.modelId`, which is what the rate card is keyed on. When you use a provider the AI SDK reports under a different name than the rate card expects (a gateway, a custom provider), pass `provider` and `model` in the options and those win.

## Source / notes

- TypeScript: `withAgentPing` and `agentPingOnFinish` in [agent-ping-typescript](https://github.com/agent-ping/agent-ping-typescript)

The AI SDK also emits OpenTelemetry spans when you pass `experimental_telemetry: { isEnabled: true }`; export those to the [OpenTelemetry](/docs/integrations/opentelemetry) endpoint for span-level traces, with or without the `onFinish` helper.
