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.
Render the parts
The shim builds every tool with the AI SDK’sdynamicTool, 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
<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.

One Next.js line
serverExternalPackages keeps Vendo’s app checker and its native and wasm dependencies out of the bundler.
next.config.ts
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.