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

@pushary/ai-sdk

v0.2.0

Published

Vercel AI SDK tools for Pushary: pause your agent until a real human approves on their phone (human-in-the-loop).

Readme

@pushary/ai-sdk

CI npm license

Full walkthrough: Human-in-the-loop for the Vercel AI SDK. Reaching your own end-users on their phones is the Pushary Partner plan.

Human-in-the-loop for the Vercel AI SDK. Give your agent one tool that pauses until a real human approves on their phone, and answers from the lock screen.

Two calls is the whole integration:

  1. enroll(externalId) once per end-user. Show them the link it returns. One tap connects their phone.
  2. Add createPusharyTools({ externalId }) to your agent. Now it can ask that person and block on the answer.

No UI to build, no polling to write, no webhooks required. Requires the Pushary Partner plan.

Install

npm i @pushary/ai-sdk ai zod

Set PUSHARY_API_KEY (get it in your dashboard).

Connect an end-user's phone (once)

import { enroll } from '@pushary/ai-sdk'

const { universalLink } = await enroll({ apiKey: process.env.PUSHARY_API_KEY! }, user.id)
// Show universalLink to the user as a button or QR. One tap turns on approvals.
// Cache the fact that they enrolled, not the link itself (it is single-use).

Give your agent a human to ask

import { generateText, stepCountIs } from 'ai'
import { createPusharyTools } from '@pushary/ai-sdk'

const { text } = await generateText({
  model: 'openai/gpt-4o',
  tools: createPusharyTools({
    apiKey: process.env.PUSHARY_API_KEY!,
    externalId: user.id, // the enrolled person who answers
  }),
  stopWhen: stepCountIs(10),
  prompt: 'Issue the refund only if a human approves it.',
})

The agent gets an askHuman tool. When it calls it, the person gets a push notification and approves, declines, picks an option, or types an answer from their phone. The tool blocks until they reply, then hands the model an unambiguous result.

Behavior that matters

  • Fail-closed. A declined, expired, or unanswered confirm is reported to the model as "not approved, do not proceed." Approval only happens on an explicit yes.
  • Serverless-safe. Each ask blocks up to 55 seconds by default (timeoutMs). The decision stays answerable for its full lifetime, so a slow human still resolves it. For waits of minutes or hours, run under a durable workflow (Inngest, Temporal, Vercel Workflow) and use a callbackUrl.
  • No double-asks on retry. The idempotency key is derived deterministically, so a retried step reuses the same decision instead of paging the human twice.

Gating a tool the model cannot skip

createPusharyTools gives the model a tool it chooses to call. That is right for "go ask someone about this", and wrong for "this must not happen without a yes", because a model that does not want to be interrupted can decline to call it.

For an enforced gate, use pusharyApproval() in the AI SDK's own toolApproval. The SDK evaluates it before the tool executes, so there is no path around it:

import { generateText } from 'ai'
import { pusharyApproval } from '@pushary/ai-sdk'

const { text } = await generateText({
  model: 'openai/gpt-4o',
  tools: { issueRefund, lookupOrder },
  // only issueRefund asks a human; lookupOrder runs untouched
  toolApproval: pusharyApproval({ externalId: user.id, tools: ['issueRefund'] }),
  prompt: 'Refund order 1234.',
})

Drop tools to gate every call. For the per-tool form:

toolApproval: {
  issueRefund: pusharyToolApproval({ toolName: 'issueRefund', externalId: user.id }),
}

Fail-closed: a denial, an expiry, or nobody answering all come back denied and the tool does not run. The decision is keyed on the tool call, so a provider-level retry resolves to the same decision instead of asking twice.

For a multi-tenant product, resolve the end-user per call:

toolApproval: pusharyApproval({ externalId: (call) => ownerOf(call.input) })

toolApproval does not exist in ai@5, so the gate needs a newer ai. The ask tool above works from ai@5 on.

API

createPusharyTools(config)

config: { apiKey, externalId, agentName?, timeoutMs?, baseUrl? }. Returns { askHuman }, a tool you pass to generateText / streamText. Merge it with your own tools:

tools: { ...createPusharyTools({ apiKey, externalId }), ...myOtherTools }

enroll(config, externalId)

config: { apiKey, baseUrl? }. Returns { token, deepLink, universalLink, expiresInSeconds }.

Under the hood

This package is a thin binding over the shared adapter kernel in @pushary/server (@pushary/server/adapters), which every Pushary framework adapter is built on. Use @pushary/server directly for any framework, @pushary/server/adapters to write your own adapter, or the Pushary MCP server to wire agents up with no code at all. See the adapters guide.

MIT

Example

A runnable example is in examples/.