@pushary/ai-sdk
v0.3.1
Published
Phone approvals for Vercel AI SDK agents. Your agent asks, your user taps Approve or Deny.
Maintainers
Readme
@pushary/ai-sdk
Phone approvals for Vercel AI SDK agents. Your agent asks, your user taps Approve or Deny.
Integration guide · Connect your customer’s phone · Report a problem
What you need
- A Pushary Partner plan, from $99 a month. Start the trial.
- An API key from Partner onboarding, set as
PUSHARY_API_KEY. - Your users install the free Pushary app (iPhone, Android). They never sign up or pay.
Quick start
npm i @pushary/ai-sdk ai zodimport { generateText, stepCountIs } from 'ai'
import { createPusharyTools, enroll } from '@pushary/ai-sdk'
const { universalLink } = await enroll({ apiKey: process.env.PUSHARY_API_KEY! }, user.id)
// Once per user: show universalLink as a button or QR code.
const { text } = await generateText({
model: 'openai/gpt-4o',
tools: createPusharyTools({ apiKey: process.env.PUSHARY_API_KEY!, externalId: user.id }),
stopWhen: stepCountIs(10),
prompt: 'Ask the customer which order they need help with.',
})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 waits until they reply, then hands the model a clear result. Cache the fact that a user enrolled, not the link itself: it is single-use.
Use createPusharyTools when your agent should ask a question. Use pusharyApproval (below) when a tool must not run without a yes. Yes or no questions can be answered from the lock screen. Choices and typed answers open the app. Legacy web links remain available.
The AI SDK's own approval docs: Tools and tool calling.
Try it before signing up
Open the no-signup browser demo. It demonstrates a human approval with an open phone page and temporary state; it does not demonstrate push delivery or durable production storage.
For a local example using the real AI SDK approval callback:
git clone https://github.com/Pushary/pushary-ai-sdk.git
cd pushary-ai-sdk
npm install
npm run build
node examples/refund.mjsUse Node.js 22 and AI SDK 7 (installed by this repo). No account, card, API key, or model provider is needed for this simulation. It checks all three outcomes:
yes: executed (simulated refund)
no: blocked (simulated refund)
unanswered: blocked (simulated refund)Read the example and try a real phone approval. The integration code is MIT-licensed; real phone delivery uses the hosted Pushary service and requires Partner access.
Get help or contribute an example.
Behavior that matters
- No answer means no. A declined, expired, or unanswered
confirmis reported to the model as "not approved, do not proceed." Approval only happens on an explicit yes. - Bounded wait. Each ask waits up to 55 seconds by default (
timeoutMs). A later answer does not restart that completed request. For delayed answers, use the saved-message recipe below or your existing workflow runner. - No double-asks on retry. 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:
import { pusharyToolApproval } from '@pushary/ai-sdk'
const { text } = await generateText({
model: 'openai/gpt-4o',
tools: { issueRefund, lookupOrder },
toolApproval: {
issueRefund: pusharyToolApproval({ toolName: 'issueRefund', externalId: user.id }),
},
prompt: 'Refund order 1234.',
})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) })Use ai@7 for the toolApproval gate examples above. This API is absent from
[email protected] and [email protected]; the package's ai >= 5.0.0 peer range also serves the
basic ask tool, which works from ai@5 on.
Runtime and approval boundaries
Set policy: false when a person must always decide. A trusted subject resolver can provide the action target, parameters, and presentation. Recipient identity comes from your authenticated application, never from model-supplied arguments. Tool retries keep their review only while the recipient and complete proposed action remain identical.
Run npm run build then node examples/refund.mjs for a real AI SDK 7 execution with simulated approval responses; --live enrolls your test phone while the refund remains simulated. This request-time gate is bounded by its timeout. For answers arriving later, persist the AI SDK's pending approval and messages in your application and resume them only after verifying the matching decision. This package does not add a durable runner.
Version 0.3.0 requires server SDK 2.1. The basic ask tool retains ai >=5 and Node.js 18 as its minimum metadata; follow the runtime requirements of your installed AI SDK version. The enforced toolApproval API requires AI SDK 7 and Node.js 22 or later. The delayed SQLite recipe needs Node.js 22.13 or later (tested on 24.3 with [email protected]); the broader peer range is not a claim that every version was tested. Do not assume nested subagent tools support the same approval API. Upgrade alongside server SDK 2.1 and finish old pending operations on their original version, because approval keys now bind the full action.
Delayed customer answers
The SQLite reference recipe saves AI SDK's native approval messages before asking the customer, reads the authoritative answer later, and resumes only the saved call. It includes atomic worker claims, exact customer/action binding, durable outputs and uncertain-execution recovery. No hosted scheduler is required.
Run npm run test:restart with Node.js 22.13 or later (tested on 24.3) to check fresh-process recovery, duplicate workers and crash handling using a simulated API. Examples are included in the npm package. The delayed recipe requires AI SDK 7; confirm/select/input in the existing ask tool remain request-time interactions.
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/.
