Skip to main content
The quickstart wires an AI SDK loop end to end. This page is the rest.

Build the pack per request

vendoTools takes the caller and returns a Promise, so it belongs inside the handler rather than at module scope.
app/api/chat/route.ts
stopWhen is not optional in practice. The AI SDK stops after one step by default, so without it the turn calls a Vendo tool and ends there — the person sees the tool fire and never gets an answer. principal is not nullable, and an auth preset’s resolver returns Principal | null. Drop the 401 and the call stops typechecking. Both imports come from the lib/vendo.ts in the quickstart. Pass sessionId alongside principal to carry your own session id into Vendo’s audit trail. Leave it out and the shim mints one per pack build.
The principal your loop passes has to be the one your wire route resolves. If they disagree, the agent reports success while the embed polls forever, because the person looking at the chat owns none of what the agent made.Resolve both from the same session, and never take a principal from the client. See Auth.

Render the parts

The shim builds every tool with the AI SDK’s dynamicTool, so useChat streams them as dynamic-tool parts rather than tool-<name>. Match on that. Where that match lives is yours. The path below is a name, not a contract — only your own chat imports it, so a components/ directory you do not have is a directory you do not need. examples/ai-sdk-agent inlines the same branch in app/page.tsx instead.
components/vendo-part.tsx
Render that part outside any <p>. The embeds root in a <div>, so a chat that wraps every message part in a paragraph puts block markup inside one — the browser reparses it, and React throws a hydration error on the message that held the screen. Use a <div>, or no wrapper at all. <VendoToolResult> handles plain data, app refs, and approval refs, so you never branch on the envelope yourself. Full contract: Embeds in your chat.
The AI SDK example chat answering a dashboard request with a generated weather comparison screen rendered inline in the assistant message

One Next.js line

serverExternalPackages keeps Vendo’s app checker and its native and wasm dependencies out of the bundler.
next.config.ts
On Next 14 the key is experimental.serverComponentsExternalPackages — same list, old name and location (renamed in Next 15). @vendoai/apps syntax-checks generated apps with esbuild, and PGlite — loaded by @vendoai/store — backs persistence. @vendoai/apps is the entry that matters: it reaches esbuild through a variable specifier the bundler cannot see, so an "esbuild" entry on its own is inert. Paste all four — vendo doctor fails E-CFG-004 on any one that is missing. The wire route needs no runtime or dynamic export, since Node is the default runtime and route handlers are already dynamic.

Two models, one key

Your loop and Vendo’s own turns each call a model. vendoModel() resolves through the Cloud gateway on your VENDO_API_KEY, and the builds behind vendo_make resolve the same way while createVendo’s models block is unset. A name you pass reaches the gateway verbatim, since there is no client-side translation of model ids. Which seat takes which name is on Model credentials. vendo doctor cannot see your loop’s model, so an install is not done until a chat turn renders a Vendo tool output.

The full example

examples/ai-sdk-agent is the stock AI SDK Next.js chatbot with this diff applied. Every added line sits between --- vendo and --- /vendo markers, so grep shows you the whole integration.