Skip to main content
Someone is on the other end. respond() is one turn of that conversation: an AI-SDK UI-message-stream Response you return from a route unchanged.

Give it hands

api() is your own API — .vendo/tools.json, layered overrides, and present-user header forwarding. tool() is anything you would rather write yourself, with its own auth model.
Label the risk. read, write, or destructive — your label is final, and it is what decides whether a call runs or stops for the person. Leave it off and the tool is ungraded, which the guard treats like destructive and asks about every time. Details: Tools and safety. Scope it to the caller. The orderId came from the model; ctx.principal is who the turn acts as. Bind the two yourself — api() forwards the caller’s credentials for you, but a hand-written tool() reaches your data on its own terms, and an unscoped lookup will act on someone else’s order no matter how the approval card was answered.

The route

Three things are doing work there:
  • user.id is the subject. Every thread, every workspace file, and every audit row is scoped to it. Vendo mints no identity of its own.
  • headers: req.headers forwards the caller’s own credentials, so a tool call reaches your API as the person who is signed in — not as the agent.
  • user is server-trusted identity the model may read ([User] in the prompt). It is facts, not instructions. Free-form background the model should treat as observation goes in context instead.

Continue the conversation

The response carries the thread id in x-vendo-thread-id. Keep it and hand it back:
A threadId is checked against the same subject that owns it. Someone else’s thread — or one that never existed — is not-found, never a silent new conversation.

Approvals, with a person present

An interactive turn can stop for the human. A destructive or ungraded call raises an approval, the stream carries the card, and the turn waits about 90 seconds for a decision. Approve and the call re-dispatches byte-for-byte; deny and the model is told so and carries on.
This is the interactive lane only. Inside run() there is nobody to ask — the approval parks and the call comes back denied. See Approvals inside run.

Call it like a function

The same agent, unattended, through the other verb.

Reference

Every RespondOptions key and its type.