Skip to content

Streaming

agentfootprint emits a discriminated union of 9 event types. Build any UX.

await agent.run('Check order', {
onEvent: (event) => {
switch (event.type) {
case 'turn_start': console.log('--- Turn started ---'); break;
case 'llm_start': console.log(`LLM call #${event.iteration}...`); break;
case 'thinking': console.log(`[thinking] ${event.content}`); break;
case 'token': process.stdout.write(event.content); break;
case 'llm_end': console.log(`\n[${event.toolCallCount} tools, ${event.latencyMs}ms]`); break;
case 'tool_start': console.log(`Running ${event.toolName}...`); break;
case 'tool_end': console.log(`Done (${event.latencyMs}ms)`); break;
case 'turn_end': console.log(`--- Done (${event.iterations} iterations) ---`); break;
case 'error': console.error(`[${event.phase}] ${event.message}`); break;
}
},
});
EventWhenKey fields
turn_startAgent begins processinguserMessage
llm_startLLM API call beginsiteration
thinkingExtended thinking contentcontent
tokenToken streamed from LLMcontent
llm_endLLM API call completestoolCallCount, content, model, latencyMs
tool_startTool execution beginstoolName, toolCallId, args
tool_endTool execution completestoolName, toolCallId, result, latencyMs
turn_endAgent donecontent, iterations, paused
errorError occurredphase, message
import { SSEFormatter } from 'agentfootprint/stream';
app.get('/chat', async (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
await agent.run(req.query.message, {
onEvent: (event) => {
res.write(SSEFormatter.format(event));
},
});
res.end();
});
const agent = Agent.create({ provider })
.streaming(true) // enables token-by-token streaming
.build();

Tool lifecycle events (tool_start, tool_end) fire even without .streaming(true) — only token events require streaming mode.