Stream output
You want a route's response to arrive incrementally rather than as a single blob at the end. Here's how to call /runs/stream and read the SSE frames.
The code
scripts/stream-hello.ts
const res = await fetch("http://127.0.0.1:3001/runs/stream", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
assistant_id: "/hello/[tenant]#agent",
input: { tenant: "acme" },
metadata: {
dawn: {
mode: "agent",
route_id: "/hello/[tenant]",
route_path: "src/app/(public)/hello/[tenant]/index.ts",
},
},
on_completion: "delete",
}),
})
if (!res.ok || !res.body) {
throw new Error(`stream failed: ${res.status}`)
}
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader()
let buffer = ""
while (true) {
const { value, done } = await reader.read()
if (done) break
buffer += value
// SSE frames are delimited by a blank line.
const frames = buffer.split("\n\n")
buffer = frames.pop() ?? ""
for (const frame of frames) {
const dataLine = frame.split("\n").find((l) => l.startsWith("data: "))
if (!dataLine) continue
const payload = JSON.parse(dataLine.slice("data: ".length))
console.log(payload)
}
}Notes
- Same envelope as
/runs/wait. The body shape is identical —assistant_id,input,metadata.dawn.{mode,route_id,route_path},on_completion: "delete". Only the endpoint differs. text/event-stream, not JSON. The response is raw SSE. Parsedata:lines yourself, or use a client likeeventsource-parser.- Use
/runs/waitwhen you don't need progress. Streaming adds parsing complexity for callers. Pick/runs/streamwhen partial output is meaningful - long agent reasoning, token-by-token text, intermediate workflow states, planning updates, or subagent activity. - Retry has limits during streams. Once a token has been emitted, the response is committed and cannot be retried. See Retry for the streaming caveat.