Skip to content

Vercel AI SDK

Installing Telem into a coding agent instead? The one-line curl detects the frameworks you already have and installs into as many as you select in one pass. This page is the JavaScript library integration for agents built on the Vercel AI SDK.

@telemai/sdk/vercel gives an AI SDK agent two tools, telem_search and telem_fetch, and attaches query lineage to every call they make. You do not build lineage data, and you do not thread a session id. The model never sees a lineage field. When your agent starts subagents, their calls appear in the console under the agent that started them.

Terminal window
npm install @telemai/sdk ai

ai version 5 or later is necessary. It is an optional peer dependency: the main @telemai/sdk entry does not need it, and only this subpath imports it. A CommonJS project needs ai 5 or 6, or Node 22.12 or later with ai 7, because ai 7 ships ES modules only.

Create an API key under API keys in the Telem console. Set it as TELEM_API_KEY, or pass it to the client in code. See Authentication for the full rules.

Build the tool set once for one conversation, and pass it to generateText, streamText or a ToolLoopAgent:

import { generateText } from "ai"
import Telem from "@telemai/sdk"
import { createTelemVercelTools } from "@telemai/sdk/vercel"
const telem = new Telem() // TELEM_API_KEY from the environment
const tools = createTelemVercelTools({ telem, harness: "my-app" })
const result = await generateText({
model,
tools,
prompt: "Is the Nintendo Museum open over New Year?",
})

harness is a stable name for your integration. It is part of every id the console shows, so choose it once and keep it. There is no default.

Build the set where the conversation starts, once per conversation: inside the request handler for a chat, not at the top of a module. A set shared by several conversations records all of them as one. See Conversation identity.

An AI SDK subagent is a generateText that a tool starts. Give your tools to createTelemVercelTools, and use the same tool set in the subagent. Nothing else is necessary: the subagent’s calls appear in the console under the tool call that started it.

import { generateText, tool } from "ai"
import { z } from "zod"
const research = tool({
description: "Research one question in depth.",
inputSchema: z.object({ task: z.string() }),
execute: async ({ task }): Promise<string> => {
const result = await generateText({ model, tools, prompt: task })
return result.text
},
})
const tools = createTelemVercelTools({ telem, harness: "my-app", tools: { research } })
await generateText({ model, tools, prompt: "Plan a Japan trip with subagents." })

Subagents started in the same turn appear side by side under the parent. A subagent that starts its own subagents nests one level deeper. Bound that depth in your own code: a subagent that receives the tool that started it can start another, and a model told to split its task will split it again at every level until the process runs out of memory. Give a subagent the tool set without the delegating tool, or count delegations and stop at a limit. One tool call is one subagent: a tool that starts several generateText runs shows them as one subagent. To keep them apart, give each run its own tools with childTelemVercelTools and a conversationId of your own. Streaming tools, written as async function*, are linked in the same way.

This automatic link needs AsyncLocalStorage. It is a Node API that lets code know which asynchronous call chain it is running in, the way a request id follows a web request through every await. Node 16.4 and later, Bun, Deno, and Cloudflare Workers with Node compatibility have it. Browsers do not, and neither does a runtime that lacks process.getBuiltinModule, which is how the package reaches it without a Node import: that means Node before 20.16 and edge runtimes without Node compatibility. telemVercelAutoNesting tells you at start-up:

import { telemVercelAutoNesting } from "@telemai/sdk/vercel"
if (!telemVercelAutoNesting) console.warn("subagents need childTelemVercelTools on this runtime")

When it is false, the tools still work. Link each subagent with one line: see Without AsyncLocalStorage.

To change what the model reads back from a search or a fetch, pass render. It applies to the root and to every subagent:

const tools = createTelemVercelTools({
telem,
harness: "my-app",
render: {
search: (response) => response.results.slice(0, 5).map((r) => ({ title: r.title, url: r.url })),
fetch: (response) => response.results[0]?.content ?? "",
},
})

To watch the calls, pass onResponse. It runs after every successful search and fetch, for the root and for every subagent, with the agent that made the call and the response:

const tools = createTelemVercelTools({
telem,
harness: "my-app",
onResponse: ({ kind, agent, response }) => {
console.log(kind, agent.conversationId, response.sessionId)
},
})

A tool of your own that calls Telem asks for the agent of its run with telemVercelAgentFor, then calls search or fetch with the execute options the AI SDK gave it. That is where the lineage comes from:

import { type TelemVercelToolExecutionOptions, createTelemVercelTools, telemVercelAgentFor } from "@telemai/sdk/vercel"
const lookup = tool({
description: "Look one thing up.",
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }, options: TelemVercelToolExecutionOptions): Promise<string[]> => {
const agent = await telemVercelAgentFor(tools, options)
const response = await agent.search(query, { goal: "lookup" }, options)
return response.results.map((r) => r.url)
},
})
const tools = createTelemVercelTools({ telem, harness: "my-app", tools: { lookup } })

Give telemVercelAgentFor and childTelemVercelTools the set that createTelemVercelTools returned, not a copy of it. Keep the tool keys telem_search and telem_fetch, and do not pass tools of your own under those two names: createTelemVercelTools refuses them, because a tool under either name would replace the one that carries the lineage. Use render to change what they return.

One agent is one conversation. The agent makes its own conversation id. If one conversation spans several requests, such as a chat where each request creates a new agent, pass the same id each time:

const tools = createTelemVercelTools({ telem, harness: "my-app", conversationId: chat.id })

Without it, each request appears in the console as a separate conversation.

  • One conversation per agent, with each search and fetch in the order it happened.
  • The turn that made each call, so several calls from one model turn stay together.
  • A subagent’s calls under the agent that started it.
  • The goal and context a call carried, when the model supplied them.

The integration sends lineage data incrementally, the same way the Telem plugins do: a subagent’s inherited context travels once, not on every call. There is nothing to configure. The client confirms that the backend supports it before it omits anything, so an older or self-hosted backend keeps receiving the full data. TELEM_INCREMENTAL=off restores full transmission on every call. It is a rollback lever, not a tuning knob.

Inside the tool that starts the subagent, ask for the subagent’s tool set with childTelemVercelTools and pass that set to the subagent. It is bound to the tool call that started it, so the subagent’s calls appear under it. Everything else stays the same, and the same line also works on a runtime that has AsyncLocalStorage, if you prefer the link to be visible in your code:

import { childTelemVercelTools, createTelemVercelTools } from "@telemai/sdk/vercel"
const research = tool({
description: "Research one question in depth.",
inputSchema: z.object({ task: z.string() }),
execute: async ({ task }, options): Promise<string> => {
const subTools = await childTelemVercelTools(tools, options) // the one line
const result = await generateText({ model, tools: subTools, prompt: task })
return result.text
},
})
const tools = createTelemVercelTools({ telem, harness: "my-app", tools: { research } })

A subagent that starts its own subagents does the same. The line above works unchanged one level down, because the set knows which tool call it runs in. The runnable example, examples/vercel-ai-agents.mjs in the SDK source, shows that line as a comment in its delegating tool.

Underneath both is createTelemVercelAgent: one agent is one conversation, agent.tools are the same two tools, and await agent.child(options) gives a subagent its own agent. Use it to name each conversation yourself.

Pass harness, a stable name for your integration. It is never defaulted.

A subagent shows as a separate conversation

Section titled “A subagent shows as a separate conversation”

Pass the tool that starts it to createTelemVercelTools, and use the same tool set inside the subagent. If telemVercelAutoNesting is false on your runtime, give the subagent await childTelemVercelTools(tools, options) instead. See Without AsyncLocalStorage.

The tools are not in the model’s tool list

Section titled “The tools are not in the model’s tool list”

Keep the tool keys telem_search and telem_fetch when you spread them. The console recognizes searches by those names.

Each request shows as its own conversation

Section titled “Each request shows as its own conversation”

Pass a stable conversationId when one conversation spans several requests. See Conversation identity.

The set was built once and shared, for example at the top of a module. Build it where each conversation starts.

childTelemVercelTools: pass the set that createTelemVercelTools returned

Section titled “childTelemVercelTools: pass the set that createTelemVercelTools returned”

A spread copy of the set, such as one with a replaced tool, is not the set. Keep the original reference for childTelemVercelTools and telemVercelAgentFor, and use render or onResponse instead of replacing a tool.

Install ai (version 5 or later) next to @telemai/sdk. Only the vercel subpath needs it.