Skip to content

SDKs and frameworks

Give your Vercel AI SDK agent the Telem search and fetch tools, with query lineage on every call.

Using a coding agent? Find the one you use on Integrations.

@telemai/sdk/vercel gives an AI SDK agent two tools, telem_search and telem_fetch. It 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. This is because ai 7 ships ES modules only.

Create an API key under API keys in the Telem console. The guided installer writes it to ~/.telem/credentials.json, which the client reads on Node 20.16 or later. You can also set it as TELEM_API_KEY, or pass it in code. See API keys 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() // environment, then ~/.telem/credentials.json
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. Limit that depth in your own code. A subagent that receives the tool that started it can start another. 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. It lets code know which asynchronous call chain it runs 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 have it. A runtime that lacks process.getBuiltinModule cannot use it either, because the package reaches it through that function, without a Node import. This 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.

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.

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. It gets 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 first gets the agent of its run with telemVercelAgentFor. Then it calls search or fetch with the execute options that the AI SDK gave it. This is how the call gets its lineage:

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. 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 sends the lineage. Use render to change what they return.

  • 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 that a call sent, when the model supplied them.

The integration sends lineage data incrementally, the same way the Telem plugins do. A subagent’s inherited context is sent once, not on every call. It needs no configuration. Before the client omits anything, it confirms that the backend supports this. So an older or self-hosted backend still gets the full data. TELEM_INCREMENTAL=off restores full transmission on every call. Use it only to roll back this behavior.

Inside the tool that starts the subagent, get the subagent’s tool set with childTelemVercelTools. Pass that set to the subagent. The set is bound to the tool call that started it, so the subagent’s calls appear under it. Everything else stays the same. The same line also works on a runtime that has AsyncLocalStorage, if you want 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 is examples/vercel-ai-agents.mjs in the SDK source. It shows that line as a comment in its delegating tool.

Both ways use createTelemVercelAgent underneath. One agent is one conversation, and agent.tools are the same two tools. await agent.child(options) gives a subagent its own agent. Use createTelemVercelAgent to name each conversation yourself.

Pass harness, a stable name for your integration. It has no default.

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.

Was this page helpful?