ConceptsStreaming

Real-Time Event Streaming

Runner.stream() provides real-time event streaming for interactive web applications, chat interfaces, and CLI tools using native JavaScript AsyncGenerator.


Why Stream Events?

In modern AI applications, users expect real-time feedback token-by-token rather than waiting 5+ seconds for a complete response.

Runner.stream() yields structured events for:

  • Token Text Deltas: Streamed partial text chunks as they arrive from the model.
  • Tool Call Lifecycle: Notifications when a tool execution starts, completes, or fails.
  • Agent Handoffs: Live alerts when control shifts from a triage agent to a specialist agent.
  • Run Completion: Final metrics, total tokens, and full message history.

Stream Event Types

Event TypePropertiesDescription
'text_delta'delta: stringNext chunk of text generated by the model.
'tool_call_started'toolCall: { name, args }Tool execution initiated by the model.
'tool_call_finished'toolCall: { name }, resultTool execution completed with output data.
'handoff'fromAgent: string, toAgent: stringActive agent transferred via handoff.
'run_complete'result: RunResultEntire execution loop completed cleanly.

Complete Streaming Code Example

import { Agent, Runner } from '@kikoyu/core';
 
const agent = Agent.builder()
  .setName('Streaming Assistant')
  .setInstructions('You write creative short stories.')
  .build();
 
const runner = new Runner();
 
async function main() {
  const eventStream = runner.stream(agent, 'Write a 3-paragraph story about space exploration.');
 
  for await (const event of eventStream) {
    switch (event.type) {
      case 'text_delta':
        process.stdout.write(event.delta);
        break;
 
      case 'tool_call_started':
        console.log(`\n\n[Tool Started]: ${event.toolCall.name}`);
        break;
 
      case 'tool_call_finished':
        console.log(`[Tool Finished]: ${event.toolCall.name}\n`);
        break;
 
      case 'handoff':
        console.log(`\n[Agent Handoff]: ${event.fromAgent} -> ${event.toAgent}\n`);
        break;
 
      case 'run_complete':
        console.log(`\n\n[Run Completed in ${event.result.messages.length} messages]`);
        break;
    }
  }
}
 
main();

Next.js App Router Server-Sent Events (SSE)

Integrate Runner.stream() with Next.js App Router API Route handlers:

import { supportAgent } from '@/lib/agents';
import { Runner } from '@kikoyu/core';
 
export async function POST(req: Request) {
  const { prompt } = await req.json();
  const runner = new Runner();
 
  const encoder = new TextEncoder();
  const readableStream = new ReadableStream({
    async start(controller) {
      for await (const event of runner.stream(supportAgent, prompt)) {
        if (event.type === 'text_delta') {
          controller.enqueue(encoder.encode(`data: ${JSON.stringify({ text: event.delta })}\n\n`));
        }
      }
      controller.enqueue(encoder.encode('data: [DONE]\n\n'));
      controller.close();
    },
  });
 
  return new Response(readableStream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  });
}

Best Practices

  1. Handle High Delta Volume: Ensure client-side UI handlers batch or throttle DOM updates if receiving rapid token deltas.
  2. Always Handle run_complete: Use run_complete to mark the stream as finished and store conversation state.