npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@avanor/sdk-vercel-ai

v0.1.3

Published

Avanor SDK — Vercel AI SDK auto-instrumentation

Readme

@avanor/sdk-vercel-ai

Avanor SDK. Vercel AI SDK auto-instrumentation.

Wraps Vercel AI SDK's generateText, streamText, and generateObject with llm.call spans carrying OTel GenAI semconv attributes, and reports AI discovery for the call shapes where it can do so honestly.

Install

npm install @avanor/sdk @avanor/sdk-vercel-ai

Add ai to your dependencies if it isn't already.

Quickstart

import { Avanor } from '@avanor/sdk';
import { instrumentVercelAI } from '@avanor/sdk-vercel-ai';

Avanor.init({
  apiKey: process.env.AVANOR_API_KEY!,
  environment: process.env.NODE_ENV ?? 'dev',
  service: 'broker-portal',
});

// Drop-in replacement for the bare `ai` functions:
const { generateText, streamText, generateObject } = instrumentVercelAI();

const result = await generateText({
  model: 'anthropic/claude-haiku-4.5', // a gateway model id
  prompt: 'Hi',
});

What this adapter fills

Two different mechanisms, feeding different pages on different timetables. One does not imply the other.

| Mechanism | Which calls | Where it lands | |---|---|---| | the llm.call span this adapter opens around every wrapped call | all three of generateText, streamText, generateObject, in every model shape | an agent event, which graduates into the Agents roster on a 30-minute reconcile schedule, with governance off | | a discovery report this adapter sends after the call completes | generateText and generateObject only, and only when model is a gateway-form string | Inventory, directly, and the same Agents roster on the same schedule |

So a streamText-only app will show up on Agents and stay absent from Inventory. That is the code behaving as designed, not a broken install.

Neither mechanism fills Activity or the Audit Log. Those come from your own allow() / guard() and attest() calls, which this adapter does not make for you. Activity additionally needs the agent switched to governed before allow() records anything. See @avanor/sdk's README for the full path per page.

Which model shape reports discovery

| model you pass | Discovery reported? | |---|---| | 'anthropic/claude-haiku-4.5' (gateway string) | Yes. The namespace before the slash is a value your own source code declared, so reporting it invents nothing. | | 'gpt-4o' (bare string) | No. There is no namespace in the input, so naming a provider would mean the adapter supplying a fact from nowhere. | | openai('gpt-4o') (provider instance) | No. The object carries the provider package's own model id, not a caller-declared namespace. |

If you pass provider instances, this adapter alone will not fill Inventory for them. They are covered only if you also install the auto rail (node --import @avanor/sdk/auto), whose network host matcher recognises api.openai.com alongside eleven other provider host rules, several of them wildcards covering more than one host. Installing this adapter does not install that rail, and vice versa. See the coverage matrix in @avanor/sdk's README for which hosts are on the list and what each producer does and does not see.

What the reported provider is, and is not. It is the namespace your model id declared. It is not a guaranteed identification of the vendor that served the inference: 'meta/llama-3.3-70b' reports meta, the creator of the weights, while the gateway may route that call to Bedrock, Groq, or anywhere else. The serving provider is only reachable through an async gateway lookup that cannot be made from inside your synchronous model call. Treat the field as "which model family this feature asked for", not as "which vendor we have a data-processing relationship with".

Why this exists

A customer calling a model gateway matches neither of the auto rail's automatic mechanisms: no vendor library is patched, and no allowlisted provider host is contacted. Their Inventory stays empty while their Agents page shows live governed agents. This adapter sits directly on the model call and already holds the provider namespace, the model, and the token counts, so it can report them.

One thing that is not automatic but does see gateway calls: if you have turned on the AI SDK's own experimental_telemetry and you run the auto rail, those spans land in Avanor's tracer provider and its span consumer picks the call up. That is an opt-in you make in your own code. If it applies to you, read the next section before installing this adapter as well.

Do not assume the rails deduplicate

If you run both node --import @avanor/sdk/auto and this adapter in the same process, one AI feature can produce two Inventory rows. The two rails resolve feature identity differently (different call-site precedence, this adapter sends an empty route where the auto rail sends the real one, and the auto rail applies a dev-mode call-site bypass this adapter does not). The rows stay distinguishable on the wire via avanor.discovery.source (explicit here, a detection-mechanism value there), but nothing merges them at write time. If you need exactly one row per feature today, run one rail.

Multi-tenant: pass the client explicitly

instrumentVercelAI() with no argument resolves its client from the module-level singleton that only Avanor.init() populates. If your process builds clients with Avanor.createClient() per tenant instead, that singleton is never populated, and the adapter silently returns the raw, uninstrumented ai functions: no spans, no discovery, no error, in every tenant.

Pass the client:

const tenantClient = Avanor.createClient({ apiKey, environment, service });
const { generateText } = instrumentVercelAI({ client: tenantClient });

There is no request-scoped mechanism today, so the client has to travel from where you construct it to where you make the model call, through your own plumbing. Build the wrapper at the narrowest scope that is guaranteed to be single-tenant, not at module scope. Do not work around this by registering one tenant's client as a process-wide default: that attributes every call to whichever tenant loaded first.

Attributes captured per call

| Attribute | Value | |---|---| | gen_ai.provider.name | 'vercel-ai' | | gen_ai.operation.name | 'generateText', 'streamText', 'generateObject' | | gen_ai.request.model | The model spec (model id when extractable) | | gen_ai.usage.input_tokens | From the result's usage (non-stream paths) | | gen_ai.usage.output_tokens | From the result's usage (non-stream paths) |

gen_ai.provider.name names the integration, not the model vendor, and it is a shipped field on rows you already have. The discovery report carries the model id's namespace separately. Both are correct; they answer different questions.

Streaming limitations (v0)

For streamText, the span closes when the wrapped function returns the stream handle, not when the stream completes. The wrapped result is still the exact Vercel AI SDK return value, so you can await result.text, await result.usage, etc., as normal.

Token-usage attributes are not captured for streamText in v0. The span carries provider/operation/model attributes only. Full streaming-completion capture (token usage, finish reason, and so on) lands in v0.5.

streamText also reports no discovery. There is no completion hook, so the only row it could emit would carry no token counts and an end time for a call that had not ended. A fabricated row is worse than a missing one in a compliance surface, so nothing is emitted until the v0.5 completion hook exists.

Other known gaps

Two build shapes still cannot load the optional ai peer, and where they cannot your model call throws

The most severe item on this page, and the only one that makes your own code fail rather than merely reporting less. 0.1.3 repaired the loader for every shape it can reach. Two shapes it cannot reach are below. Both were measured after the fix, and both fail on every version of this package, including the one you are reading this from.

  • Emitting an ES module from your bundler (output.module: true with experiments.outputModule), on an otherwise-working config. Internal cause: Dynamic require of "ai" is not supported. Both loader branches need a require your bundler has removed, so no branch is left that can succeed.
  • Relocating the emitted bundle outside the directory tree that holds node_modules/ai. The same artifact that worked before the move fails after it. Internal cause: Cannot find module 'ai', because the load resolves relative to the OUTPUT bundle's location rather than to your source. Next.js output: 'standalone' and its file tracing relocate output in exactly that way.

In both, instrumentVercelAI() logs that ai is not installed and returns wrappers that throw on call:

@avanor/sdk-vercel-ai: `ai` package not installed. Run `npm install ai`
(or `pnpm add ai`) to use instrumentVercelAI().

That message is wrong when you already have ai installed, and it sends the debugging in the wrong direction. The real cause is the load path, not a missing package.

Those two lines are the only thing you will see. The underlying loader error is caught and discarded by loadAi(), which latches a flag and returns undefined, so the internal causes named anywhere on this page (Dynamic require of "ai" is not supported, Cannot find module 'ai') will not appear in your logs. Verified: a consumer run against the built dist with ai genuinely installed emitted neither string. Do not grep for them to decide whether this applies to you. Grep for `ai` package not installed.

What you can do. Keep this package out of the bundle: add @avanor/sdk-vercel-ai itself to your bundler's externals, or consume the CommonJS entry. Neither shape above is something this package can observe or resolve from inside a bundle, so externalizing it is the answer for both. Adding ai to externals does nothing, because a dynamic require creates no bundler dependency for that entry to match. Next.js externalizes node_modules packages in its server build by default, which would put you in the externalized case, but that is read off Next.js's documented default and has not been confirmed against a real app.

Turbopack has been tested by nobody, in either direction, before 0.1.3 or after.

0.1.3 also introduces one new build-time warning. A bundler that parses dist/index.mjs now emits module.createRequire failed parsing argument. where it previously emitted nothing. The peer still loads. The warning is the bundler reporting that it could not statically analyse the call.

What 0.1.3 repaired, and what <= 0.1.2 still does

These tables are kept rather than deleted because they are not history for everyone: if you are pinned to 0.1.2 or earlier, they describe the package you have. Every "on <= 0.1.2" column below is a measurement of 0.1.0, 0.1.1 and 0.1.2, all of which carry the identical pre-fix loader. The gap has been present since a2f8ef6d (2026-05-20); 0.1.3 is the first published version that closes it.

On <= 0.1.2 this package loaded its optional ai peer through a bare require() call that whole classes of ordinary install could not run. It was not "ESM is broken": some ESM shapes worked and some CommonJS shapes did not. What decided the outcome was which of this package's two built files your toolchain parsed, dist/index.mjs or dist/index.cjs, and not which module system your own app was written in.

Unbundled, plain Node:

| how you load the adapter | on <= 0.1.2 | on 0.1.3 | |---|---|---| | require('@avanor/sdk-vercel-ai'), which resolves dist/index.cjs | works. Verified end to end against the real ai package. | works | | import '@avanor/sdk-vercel-ai', which resolves dist/index.mjs | fails. Internal cause: Dynamic require of "ai" is not supported. | works |

Bundled, measured on webpack 5 with target: 'node' across eight configurations:

| what the bundler parses | on <= 0.1.2 | on 0.1.3 | |---|---|---| | dist/index.mjs | works. | works | | dist/index.cjs | fails. Internal cause: Cannot find module 'ai'. Unlike the runtime error, the accompanying Critical dependency: require function is used in a way in which dependencies cannot be statically extracted warning IS visible, at BUILD time. | works, and that Critical dependency warning is still emitted |

Note that on <= 0.1.2 bundling inverts the unbundled answer: unbundled it is the CommonJS file that works, bundled it is the ESM file. Those eight configurations were 2 files x 2 modes x 2 externals settings, and across them the only variable that moved the outcome was which of the two files webpack parsed: not the mode, and not whether ai was listed in externals. Two further independences were observed separately rather than inside that matrix, so treat them as weaker evidence: your own app's module system, and the webpack version.

0.1.3's loader tries the ambient require first and falls back to createRequire when that throws, which covers both built files instead of one each. Measured across twelve configurations against the real ai package, both with the peer installed and with it absent: four in plain Node with no bundler involved, and eight across four webpack configurations (webpack 5.109.2, target: 'node'). Optionality is preserved. With ai genuinely absent, every one of those shapes still behaves the way an optional peer must: the module loads, and only calling the wrapper throws. The load failure stays caught, not fatal at import.

What 0.1.3 does not repair is the two shapes at the top of this section. They fail on <= 0.1.2 and on 0.1.3 alike.

Smaller gaps

  • A model call that throws reports no discovery. The report is emitted only after a successful call, so a feature whose calls always fail never appears in Inventory.
  • Object-form models report no discovery. The model object does expose a provider field, but its values are sub-provider-qualified ('openai.chat', 'anthropic.messages') and using them raw would fragment one vendor across several Inventory rows. Wiring it needs a canonicalisation decision first.

Zero false-positive guarantee

Scoped, deliberately, to the case where this package successfully loaded ai. Where it did, the guarantee is unconditional: each wrapped function returns the exact same value as the underlying ai function. No wrapping, no transformation, no extra fields. The discovery report is fire-and-forget and fully isolated, so nothing in it, including a customer-supplied client whose reportDiscovery throws, can change what your generateText returns or throws. That isolation is a property of the discovery report specifically, which sits behind its own try/catch. It is not claimed for the span path: startActiveSpan, setAttribute, setStatus and the end() in the finally are not individually wrapped, so a throwing tracer would propagate.

Where it did NOT load ai, there is no wrapped ai function to return a value from. instrumentVercelAI() returns wrappers that throw on call, so your model call fails rather than returning anything. That is the peer-load gap documented above, not a property of the wrapping, and it is the only way in which installing this package changes the outcome of your own code. On 0.1.3 two build shapes reach it; on <= 0.1.2 many more do. Establish which case you are in before treating the guarantee as covering you.

Docs

There is no Vercel AI recipe page yet. This adapter is documented on the auto-discovery page, which covers which surfaces each install fills and which it does not: https://www.avanor.ai/docs/sdk/auto-discovery

The exported types for the whole SDK family are at https://www.avanor.ai/docs/sdk/api-reference, and the core install is at https://www.avanor.ai/docs/sdk/quickstart.

License

Avanor Source License v1 (BSL-modeled, 4-year Apache 2.0 sunset). See LICENSE.