Tool calls and agent output — structured events, not free text
Free-text streams can't drive UI state for tool calls, errors, or completion.
nestjs-realtime-stream-structured-agent-events
Why it matters
| Stake | If ignored |
|---|---|
| Bad UX |
|
| Blind in prod |
|
How to fix
An agent's response streams as a sequence of typed events: text-delta, tool-call-started, tool-call-completed, tool-call-failed, run-completed. Not free text the client has to parse.
Project AI response stream events into this contract — do not persist or forward raw provider message payloads. Tool result fields must come from schema-validated tool output (see the related tool-output rule).
Examples
ts
sse.addEventListener('message', (e) => {
appendText(e.data);
});ts
// libs/workspace/data-access/src/lib/run-events.ts
export type RunEvent =
| { type: 'text-delta'; delta: string }
| { type: 'tool-call-started'; toolCallId: string; tool: string; args: unknown }
| { type: 'tool-call-completed'; toolCallId: string; result: SearchSiteOutput /* per-tool outputSchema */ }
| { type: 'tool-call-failed'; toolCallId: string; error: string }
| { type: 'run-completed'; runId: string; tokensUsed: number }
| { type: 'run-failed'; runId: string; error: string };
// frontend
sse.addEventListener('message', (e) => {
const event = JSON.parse(e.data) as RunEvent;
switch (event.type) {
case 'text-delta': appendText(event.delta); break;
case 'tool-call-started': showToolSpinner(event.tool); break;
case 'tool-call-completed': showToolResult(event.toolCallId, event.result); break;
// ...
}
});