> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vendo.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Embeds in your chat

> The versioned envelope a vendo_* tool returns, and the three React embeds that turn it into a rendered surface inside your own chat.

A `vendo_*` tool answers your loop with either plain data or a small versioned ref. One component turns that ref into the right surface, so your loop never learns what an app or an approval is.

## The dispatch

<svg viewBox="0 0 720 240" role="img" aria-label="A tool output enters VendoToolResult and leaves as nothing, an app embed, or an approval embed" style={{ width: "100%", height: "auto" }}>
  <rect x="8" y="94" width="150" height="52" rx="10" fill="#faf9fc" stroke="#e9e6f1" />

  <text x="83" y="117" textAnchor="middle" fontFamily="monospace" fontSize="13" fill="#4b4857">part.output</text>
  <text x="83" y="134" textAnchor="middle" fontFamily="sans-serif" fontSize="11" fill="#7c7989">from a vendo\_\* tool</text>

  <path d="M158 120h52" stroke="#a8a5b4" strokeWidth="1.5" fill="none" />

  <path d="m204 115 8 5-8 5" stroke="#a8a5b4" strokeWidth="1.5" fill="none" />

  <rect x="212" y="88" width="168" height="64" rx="12" fill="#f5f1ff" stroke="#ddd0ff" />

  <text x="296" y="114" textAnchor="middle" fontFamily="monospace" fontSize="12.5" fill="#4a22bd">VendoToolResult</text>
  <text x="296" y="132" textAnchor="middle" fontFamily="sans-serif" fontSize="11" fill="#6c3bff">reads kind</text>

  <path d="M380 120h40v-72h48" stroke="#a8a5b4" strokeWidth="1.5" fill="none" />

  <path d="M380 120h88" stroke="#a8a5b4" strokeWidth="1.5" fill="none" />

  <path d="M380 120h40v72h48" stroke="#a8a5b4" strokeWidth="1.5" fill="none" />

  <path d="m462 43 8 5-8 5M462 115l8 5-8 5M462 187l8 5-8 5" stroke="#a8a5b4" strokeWidth="1.5" fill="none" />

  <rect x="478" y="26" width="234" height="44" rx="10" fill="#ffffff" stroke="#e9e6f1" />

  <text x="494" y="45" fontFamily="sans-serif" fontSize="12.5" fill="#15141b">plain data</text>
  <text x="494" y="61" fontFamily="sans-serif" fontSize="11" fill="#7c7989">renders nothing, your model already has it</text>

  <rect x="478" y="98" width="234" height="44" rx="10" fill="#ffffff" stroke="#ddd0ff" />

  <text x="494" y="117" fontFamily="monospace" fontSize="11.5" fill="#4a22bd">vendo/app-ref\@1</text>
  <text x="494" y="133" fontFamily="sans-serif" fontSize="11" fill="#7c7989">the app embed, building inline</text>

  <rect x="478" y="170" width="234" height="44" rx="10" fill="#ffffff" stroke="#e2dfec" />

  <text x="494" y="189" fontFamily="monospace" fontSize="11.5" fill="#4b4857">vendo/approval-ref\@1</text>
  <text x="494" y="205" fontFamily="sans-serif" fontSize="11" fill="#7c7989">the approve or deny card</text>
</svg>

## The envelope

| Output                 | What happened                                         | Fields                                 |
| ---------------------- | ----------------------------------------------------- | -------------------------------------- |
| plain data             | the guarded call executed cleanly                     | whatever your action returned          |
| `vendo/app-ref@1`      | `vendo_make` accepted a build that is still streaming | `appId`, `title`, `status: "building"` |
| `vendo/approval-ref@1` | the call parked for approval                          | `approvalId`, `summary`                |

`status` on an app ref is always `"building"`, win or lose. A build that fails terminally is never wrapped in one, so the ref never means done.

Readers tolerate unknown extra fields. Anything additive stays inside `@1`, and a breaking change bumps the kind.

## The three embeds

Import all three from `@vendoai/vendo/react`, which re-exports them so you need no second package. Each reads the wire through the surrounding provider and polls on its own until it reaches a terminal state.

| Component                       | Renders                                                                              | Reaches                                                |
| ------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------ |
| `<VendoToolResult output>`      | any `vendo_*` output, dispatched to the right embed below, or nothing for plain data | one of the two below                                   |
| `<VendoAppEmbed refValue>`      | the build beat while the build streams, then the live app                            | a mounted app, or the build's own failure with a retry |
| `<VendoApprovalEmbed refValue>` | the approve or deny card, resolving in place                                         | `"pending"`, `"executed"`, `"declined"`, `"expired"`   |

None of the three takes config props beyond the one shown. Theme and behavior come from the provider, and a failure speaks the failed or expired vocabulary rather than a silent blank.

Default to `<VendoToolResult>` wherever you render a finished tool part. Reach for the other two only when you hold a ref that did not come from a live tool call, such as one you stored and want to re-render later.

## Set up the provider

Wrap your chat once in `VendoProvider`, pointed at the wire route. Auth rides your host session cookie and theme rides the `--vendo-*` tokens, so the embeds render on-brand.

```tsx app/page.tsx focus={5-7} theme={null}
import { VendoProvider, VendoToolResult } from "@vendoai/vendo/react";

export default function Chat() {
  return (
    <VendoProvider>
      {/* your chat, and for each finished tool part: */}
      <VendoToolResult output={part.output} />
    </VendoProvider>
  );
}
```

`baseUrl` defaults to `/api/vendo`, so pass it only when your wire route is mounted somewhere else. The [headless hooks](/reference/hooks) need the same wrapper, so this step covers both.

## Approvals never block the loop

A guarded call that needs approval does not throw and does not stall your turn. The tool returns an approval ref right away, your model reads "pending, the person has to approve this", and the call itself parks server-side.

When the person approves in the card, the wire runs the parked call and the card resolves in place. Deny throws it away.

<Frame>
  <img src="https://mintcdn.com/vendo/Bl9khJxYuQX2mLio/images/existing-agents/embed-approval.png?fit=max&auto=format&n=Bl9khJxYuQX2mLio&q=85&s=bdcc39b49d44eb2c4acfc0a766c3ce91" alt="An approval card inside a host chat thread, naming the recipient and the message body, with Approve and Deny buttons and a remember this decision control" width="448" height="445" data-path="images/existing-agents/embed-approval.png" />
</Frame>

A pending approval expires after 60 minutes on a sweep, and the card then reads "expired". Change it with `createVendo({ guard: guard({ approvals: { parkedCallTtlMs } }) })`, where `0` means never.

## Dispatching it yourself

`parseVendoToolEnvelope(output)` returns the typed envelope or `null`, which is exactly what `<VendoToolResult>` calls. Use it server-side, or anywhere you want the branch in your own code.

```ts focus={3-4} theme={null}
import { parseVendoToolEnvelope } from "@vendoai/core";

const envelope = parseVendoToolEnvelope(output);
if (envelope?.kind === "vendo/app-ref@1") saveForLater(envelope.appId);
```

It is a runtime value rather than a type, so it ships from `@vendoai/core`. Add that package to your dependencies to import it.

A malformed envelope returns `null` too. The tool pack is the only writer, so a bad shape is a bug there rather than something your chat should half-render.
