Chat and streaming
Ask a question over HTTP and read the answer as a stream of typed server-sent events.
Ask
POST /api/chatThe body carries the question, the connection to ask it against, and optionally a thread to continue and a model to use. Omitting the model uses the deployment default. See choosing a model.
The response is a text/event-stream.
Events
Each event has a type. Read them as a state machine rather than concatenating everything into a string.
| Event | Meaning |
|---|---|
token | A fragment of the assistant's prose. Append in order. |
tool_call | The agent invoked a tool. Carries the tool name and arguments. |
interrupt | The run paused for input, most commonly SQL approval. |
chart | A rendered figure. Carries its spec and data. |
usage | Token and credit accounting for the turn. |
done | The turn is complete. Nothing further will arrive. |
Always handle interrupt
A client that ignores interrupt will appear to hang: the stream is open and
healthy, but the run is waiting on a decision that is never coming.
Resuming after an interrupt
POST /api/chat/resumePost the decision (approve, approve-with-edits, or reject) along with the
identifiers from the interrupt event. The run continues on the same thread
with its context intact, and a new stream opens for the remainder.
See SQL approval for what the three outcomes do.
A minimal client
const res = await fetch("/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ message, configKey }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
for (;;) {
const { done, value } = await reader.read();
if (done) break;
for (const line of decoder.decode(value).split("\n")) {
if (!line.startsWith("data:")) continue;
const event = JSON.parse(line.slice(5));
switch (event.type) {
case "token":
appendText(event.value);
break;
case "chart":
renderChart(event.spec);
break;
case "interrupt":
await promptForApproval(event);
break;
}
}
}