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

@zenrows/ai-sdk-tools

v0.1.1

Published

Zenrows web scraping tools for the Vercel AI SDK: scrape any URL, extract structured data, and take screenshots, with JavaScript rendering and anti-bot bypass.

Readme

Zenrows tools for the Vercel AI SDK

Give your model the ability to read any page on the web — including JavaScript-rendered, anti-bot protected and geo-restricted ones — with three tools it can call from generateText and streamText.

npm install @zenrows/ai-sdk-tools

Requires ai v7 and zod as peer dependencies, and Node 22+.

Quick start

import { generateText, stepCountIs } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { createZenrowsTools } from '@zenrows/ai-sdk-tools';

const zenrows = createZenrowsTools({ apiKey: process.env.ZENROWS_API_KEY! });

const { text } = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  prompt: 'Read https://news.ycombinator.com and list the top three stories.',
  tools: zenrows,
  stopWhen: stepCountIs(5),
});

console.log(text);

Get an API key at app.zenrows.com. Set it as ZENROWS_API_KEY, or pass it however your app handles secrets — the key is only ever read by the factory, never by the model.

The tools

| Tool | What the model uses it for | | --- | --- | | scrapeUrl | Read a whole page as Markdown, plain text or HTML | | extractData | Pull named fields out of a page as JSON, without reading all of it | | takeScreenshot | Capture the page as an image a vision model can look at |

createZenrowsTools returns all three. Import them individually if you only want one:

import { scrapeUrl } from '@zenrows/ai-sdk-tools';

const result = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  prompt: 'Summarise https://example.com',
  tools: { scrapeUrl: scrapeUrl({ apiKey: process.env.ZENROWS_API_KEY! }) },
  stopWhen: stepCountIs(3),
});

Next.js route handler

// app/api/chat/route.ts
import { streamText, stepCountIs } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { createZenrowsTools } from '@zenrows/ai-sdk-tools';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: anthropic('claude-sonnet-4-5'),
    system: 'You are a research assistant. When asked about a URL, read it before answering.',
    messages,
    tools: createZenrowsTools({ apiKey: process.env.ZENROWS_API_KEY! }),
    stopWhen: stepCountIs(10),
  });

  return result.toUIMessageStreamResponse();
}

Options

Every option is set once, on the factory. The model never sees them.

| Option | Default | What it does | | --- | --- | --- | | apiKey | — | Required. Your Zenrows API key. | | adaptiveStealth | true | Zenrows starts with the cheapest request that works and escalates only when the target needs it. | | defaultResponseType | 'markdown' | Format scrapeUrl returns when the model does not ask for one. | | maxContentLength | 100_000 | Characters of page content handed to the model before truncation. | | concurrency | 5 | Concurrent requests to Zenrows. | | retries | 0 | Retries on 422/503/504 and network errors. |

On Adaptive Stealth

Zenrows can decide per request whether a page needs JavaScript rendering or a premium proxy, and bills for the configuration that worked. That is on by default here, which is why the model is not given a jsRender or premiumProxy knob to guess at.

Zenrows rejects adaptive stealth combined with an explicit tier flag, so an option that needs a browser — a screenshot, a wait-for-selector, a country proxy — drops it for that one call and sets what the option actually needs. The model cannot produce a request Zenrows will refuse.

Turn it off with adaptiveStealth: false if you would rather pay a flat, predictable cost per request and accept that protected pages will fail.

On content length

A scraped page goes into the model's context and you pay for every character of it. HTML routinely runs past 80,000 characters for an ordinary page, so scrapeUrl truncates at maxContentLength and sets truncated: true rather than quietly sending a large bill. Prefer extractData when you know which fields you want.

What the tools return

scrapeUrl returns { content, truncated, url, finalUrl, creditsSpent }. extractData returns { data, url, creditsSpent }. Both return { error } instead when the call fails, with a message the model can act on — they do not throw, so a failed scrape costs a step rather than the run.

takeScreenshot returns the image to your code as base64 in { image, mediaType, bytes }, and hands the model a real image part via toModelOutput, so a vision-capable model actually sees the page rather than a base64 string.

Cost

Zenrows bills per request. Adaptive stealth means you pay for the configuration that succeeded rather than for the most expensive one up front. Every tool result carries creditsSpent, so the model can report what a piece of research cost.

Checking it against the live API

pnpm check:live runs every tool against Zenrows directly, without a model or an LLM provider key — it only needs ZENROWS_API_KEY. pnpm example runs the same tools through a real generateText tool-call loop and additionally needs ANTHROPIC_API_KEY.

Links

License

MIT