Guides
Streaming
Consuming an agent run as live events with .stream().
agent.stream({ prompt }) runs the exact same loop as .generate() (see
Agents), but instead of silently draining it and
returning only the final result, it exposes every lifecycle event live as an
AsyncGenerator<AgentEvent>:
import { Agent, tool, type AgentEvent } from "chai-ai";
import { google } from "@chai-ai/google";
import { z } from "zod";
const calculator = tool({
description: "Performs a single arithmetic operation on two numbers.",
inputSchema: z.object({
operation: z.enum(["add", "multiply"]),
a: z.number(),
b: z.number(),
}),
execute({ operation, a, b }) {
return operation === "add" ? a + b : a * b;
},
});
const agent = new Agent({
name: "calculator-agent",
instructions: "Always use the calculator tool for arithmetic instead of computing it yourself.",
model: google("gemini-3.1-pro-preview"),
tools: { calculator },
});
for await (const event of agent.stream({ prompt: "What is 47 multiplied by 23?" })) {
handleEvent(event);
}Because .generate() and .stream() share one engine, switching between
them doesn't change agent behavior — only whether you see intermediate steps
as they happen.
Event types
AgentEvent (chai-ai) is a discriminated union on type:
| Event | When |
|---|---|
run_started | Once, at the very start of the run |
text_delta | Incremental text chunks as the model streams its response |
tool_started | Right before a tool call executes (toolName, args) |
tool_completed | After a tool call finishes (result, or error) |
handoff_started | When control transfers to another agent — see Handoffs |
guardrail_triggered | When an input/tool/output guardrail blocks, requires approval, or resolves — see Guardrails |
run_completed | Once, at the end of a successful run — carries the final text/output |
run_failed | Once, if the run throws — carries error |
The final event yielded is always run_completed or run_failed, which
already carries the finished text / output / error, so there's no
separate result-retrieval step after the loop ends.
Handling events
function handleEvent(event: AgentEvent) {
switch (event.type) {
case "text_delta":
process.stdout.write(event.delta);
break;
case "tool_started":
console.log(`\n[tool_started] ${event.toolName}(${JSON.stringify(event.args)})`);
break;
case "tool_completed":
console.log(`[tool_completed] ${event.toolName} -> ${JSON.stringify(event.result)}`);
break;
case "handoff_started":
console.log(`\n[handoff_started] ${event.fromAgent} -> ${event.toAgent}`);
break;
case "guardrail_triggered":
console.log(`\n[guardrail_triggered] ${event.guardrailName}: ${event.action}`);
break;
case "run_completed":
console.log(`\n[run_completed] runId=${event.runId}`);
break;
case "run_failed":
console.log(`\n[run_failed] ${event.error}`);
break;
// run_started: not logged, just marks the beginning of the run.
}
}See the runnable version of this example in
examples/streaming.