Skip to main content
A long piece of work with a progress view is still run(). The Turn it hands back is awaitable and iterable: run.events is the live feed, and awaiting the same object gives you the TurnResult at the end.
Vendo’s chat UI is not handed over here, deliberately: this lane gives you an event stream and a result, and the surface is yours to build. For a chat surface with no work at all, handler() plus useVendoChat is one mount; for Vendo’s own thread, overlay, and generated screens, see Vendo’s Full-Stack Agent.

The route

app/api/reports/route.ts
Three details carry their weight.
  • run.threadId is available immediately, before the first event, so the header goes out with the response rather than after it.
  • signal: req.signal ties the run to the connection. The browser closes the tab, the request aborts, the run stops.
  • await run after the loop is the result. Iterating the events does not consume it; the same object is both.
run.events has one reader. Attaching a second one while the first is reading throws, so forward the feed from this route and fan out in your own code.

What comes over the wire

run.events yields RunEvents, each tagged by type. Forward them whole, as above, and branch in your UI. A new event type then reaches your client the day it ships, instead of being dropped by a server that only knew five.

Ending on the result

The TurnResult is the honest close: status, the model’s own text, the toolCalls it made, threadId, turnId, and usage. One status decides which of the four screens you draw.
app/reports-client.ts
A run that parked for a human comes back interrupted, and the cards are on interruptions. Carrying on is resume(decisions) on the server-side object, not a fresh run() — the turn picks up from where it stopped.

Run

The result shape, typed output, and the usage you meter on.

Telemetry

The same runs, seen from your Cloud console.