If your agent cannot
import the package, because it runs in another service,
another language, or an MCP client you don’t own, take the MCP door
instead. If you have no agent yet, Vendo brings one — harness,
model, and chat surface included.
Ready to wire it: Quickstart.
The composition
You runcreateVendo and mount the wire route the same way any Vendo host
does. What you leave out is Vendo’s chat loop and chat UI. The wire keeps
serving apps, approvals, and connected accounts to the components in your own
chat.
lib/vendo.ts and exports resolvePrincipal, so your chat route, your wire
route, and the tool pack all resolve the caller the same way. If init wired an
auth preset instead, it wrote auth in place of principal — the two fill the
same seam, and createVendo refuses a config that sets both.
Step 2 has each form.
Everything init writes is listed on vendo init.
Then build the pack per framework:
@vendoai/vendo/ai-sdkexportsvendoTools(vendo, { principal }), an AI SDKToolSetforstreamTextandgenerateText. Built per request, because tool execution needs a principal-scoped context.@vendoai/vendo/mastraexportsvendoMastraTools(vendo), the same pack in Mastra’screateToolshape. A Mastra agent definition is static, so the principal rides the request context instead.
What’s in the pack
Every tool is namespacedvendo_*, so nothing collides with a tool of your
own. A host action called host_x ships as vendo_host_x.
include and exclude trim the pack by final tool name, exact match. Leave
include unset and you get everything; exclude wins over include.
The in-process pack carries no vendo_apps_* tool. Moving a view your agent
already made is done from your own code with the app id, not from the loop.
One principal, two places
Apps and approvals belong to a principal. Your chat turn creates them as whoever your loop passed:vendoTools(vendo, { principal }) on AI SDK, the
request-context entry on Mastra. The components in your chat read them back
through the wire route, as whoever that route’s principal resolves.
If those two disagree, everything the agent makes is invisible to the person
looking at it. The app embed polls “pending” forever while the agent reports
success. Resolve both from the same session, and never take a principal from the
client. Auth presets and the principal seam: Auth.
Approvals don’t block you
A guarded call that needs approval doesn’t throw and doesn’t stall the loop. The tool returns an approval ref immediately, so your model reads “pending, the person has to approve this”. The real call parks server-side. When the person approves in<VendoApprovalEmbed>, the wire runs it and the card resolves in
place. Deny throws the parked call away.
Parked calls expire on a sweep, 60 minutes by default:
createVendo({ guard: guard({ approvals: { parkedCallTtlMs } }) }), where 0
means never. Your agent finds out on some later turn if it matters.
The envelope contract and the three components that render it live on Embeds
and envelopes.
Teaching your agent to use it
The system-prompt block that teaches a model when a screen beats a paragraph, and what thevendo_make receipt does and does not let it say, is on
Instructions. How the view gets built, checked, and
placed in a slot is on Generated UI.
Where the guard sits
Every call runs as the signed-in person, through the same guard-bound registry Vendo’s own surfaces use. Host actions reach your routes through theactAs
seam, so no agent bearer token is ever forwarded and no credential is
duplicated. Generated views render in an iframe with connect-src 'none', so a
view reaches no host tool directly; its interactions ride the wire back through
the guard, never through your loop. Full picture: Tools and
safety.
Next
Quickstart
Five steps: init, one spread, one component, first screen.
AI SDK notes
Per-request builds,
dynamic-tool parts, and the Next.js bundler line.Mastra notes
Static agent definitions, the request-context principal, the model pin.
Embeds and envelopes
The envelope a
vendo_* tool returns, and what renders it.