Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/everruns/everruns/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Everruns uses Server-Sent Events (SSE) to stream real-time updates about session execution. The SDK provides async iterators for idiomatic event handling.

Basic streaming

Stream all events from a session:
for await (const event of client.sessions.streamEvents('session_01234567-...')) {
  console.log('Event:', event.type);
  console.log('Data:', event.data);
}

Event types

Input events

User messages submitted to the session:
for await (const event of client.sessions.streamEvents(sessionId)) {
  if (event.type === 'input.message') {
    console.log('User message:', event.data.message.content[0]?.text);
  }
}

Output events

Agent responses with streaming support:
for await (const event of client.sessions.streamEvents(sessionId)) {
  switch (event.type) {
    case 'output.message.started':
      console.log('Agent is thinking...');
      break;
    
    case 'output.message.delta':
      // Stream text as it arrives
      process.stdout.write(event.data.delta);
      break;
    
    case 'output.message.completed':
      // Get complete message
      console.log('\nFinal message:', event.data.message.content[0]?.text);
      break;
  }
}

Tool execution events

Monitor tool calls in real-time:
for await (const event of client.sessions.streamEvents(sessionId)) {
  switch (event.type) {
    case 'tool.started':
      console.log('Tool called:', event.data.tool_call.name);
      console.log('Arguments:', event.data.tool_call.arguments);
      break;
    
    case 'tool.completed':
      if (event.data.success) {
        console.log('Tool result:', event.data.result);
      } else {
        console.error('Tool error:', event.data.error);
      }
      break;
  }
}

Turn lifecycle events

Track turn execution:
for await (const event of client.sessions.streamEvents(sessionId)) {
  switch (event.type) {
    case 'turn.started':
      console.log('Turn started:', event.data.turn_id);
      break;
    
    case 'turn.completed':
      console.log('Turn completed in', event.data.duration_ms, 'ms');
      console.log('Iterations:', event.data.iterations);
      break;
    
    case 'turn.failed':
      console.error('Turn failed:', event.data.error);
      console.error('Error code:', event.data.error_code);
      break;
    
    case 'turn.cancelled':
      console.log('Turn cancelled:', event.data.reason);
      break;
  }
}

Session state events

Monitor session status:
for await (const event of client.sessions.streamEvents(sessionId)) {
  switch (event.type) {
    case 'session.activated':
      console.log('Session became active');
      break;
    
    case 'session.idled':
      console.log('Session became idle');
      console.log('Token usage:', event.data.usage);
      break;
  }
}

Filtering events

Filter by type

Only receive specific event types:
// Only turn lifecycle events
for await (const event of client.sessions.streamEvents(sessionId, {
  types: ['turn.started', 'turn.completed', 'turn.failed']
})) {
  console.log('Turn event:', event.type);
}

Exclude events

Remove unwanted events:
// Get all events except deltas (reduce noise)
for await (const event of client.sessions.streamEvents(sessionId, {
  exclude: ['output.message.delta', 'reason.thinking.delta']
})) {
  console.log('Event:', event.type);
}

Combine filters

Use both positive and negative filters:
// Only turn events, but not failures
for await (const event of client.sessions.streamEvents(sessionId, {
  types: ['turn.started', 'turn.completed', 'turn.failed'],
  exclude: ['turn.failed']
})) {
  console.log('Turn event:', event.type);
}

Resume from event ID

Continue streaming from a specific event:
let lastEventId: string | null = null;

try {
  for await (const event of client.sessions.streamEvents(sessionId)) {
    lastEventId = event.id;
    console.log('Event:', event.type);
  }
} catch (error) {
  // Reconnect from last event
  console.log('Reconnecting from event:', lastEventId);
  
  for await (const event of client.sessions.streamEvents(sessionId, {
    since_id: lastEventId
  })) {
    console.log('Event:', event.type);
  }
}
The SDK automatically handles connection cycling and reconnection with since_id to ensure no events are missed.

Extended thinking events

Monitor chain-of-thought reasoning (when using models with extended thinking):
for await (const event of client.sessions.streamEvents(sessionId)) {
  switch (event.type) {
    case 'reason.thinking.started':
      console.log('Model is thinking...');
      break;
    
    case 'reason.thinking.delta':
      // Stream reasoning content
      console.log('Thought:', event.data.delta);
      break;
    
    case 'reason.thinking.completed':
      // Get complete reasoning
      console.log('Final thinking:', event.data.thinking);
      break;
  }
}

LLM generation events

Inspect full LLM API calls:
for await (const event of client.sessions.streamEvents(sessionId)) {
  if (event.type === 'llm.generation') {
    console.log('Model:', event.data.metadata.model);
    console.log('Input tokens:', event.data.metadata.usage.input_tokens);
    console.log('Output tokens:', event.data.metadata.usage.output_tokens);
    console.log('Duration:', event.data.metadata.duration_ms, 'ms');
    
    if (event.data.metadata.success) {
      console.log('Response:', event.data.output.text);
    } else {
      console.error('Error:', event.data.metadata.error);
    }
  }
}

Real-time usage tracking

Track token usage as it accumulates:
let inputTokens = 0;
let outputTokens = 0;

for await (const event of client.sessions.streamEvents(sessionId)) {
  if (event.type === 'llm.generation' && event.data.metadata.usage) {
    inputTokens += event.data.metadata.usage.input_tokens;
    outputTokens += event.data.metadata.usage.output_tokens;
    
    console.log(`Tokens: ${inputTokens} in, ${outputTokens} out`);
  }
  
  if (event.type === 'session.idled' && event.data.usage) {
    // Get final cumulative usage
    console.log('Final usage:', event.data.usage);
    inputTokens = event.data.usage.input_tokens;
    outputTokens = event.data.usage.output_tokens;
  }
}

Error handling

Handle connection errors

try {
  for await (const event of client.sessions.streamEvents(sessionId)) {
    console.log('Event:', event.type);
  }
} catch (error) {
  if (error instanceof EverrunsError) {
    switch (error.status) {
      case 404:
        console.error('Session not found');
        break;
      case 401:
        console.error('Authentication required');
        break;
      default:
        console.error('Stream error:', error.message);
    }
  } else {
    console.error('Unexpected error:', error);
  }
}

Handle turn failures

for await (const event of client.sessions.streamEvents(sessionId)) {
  if (event.type === 'turn.failed') {
    switch (event.data.error_code) {
      case 'llm_error':
        console.error('LLM error:', event.data.error);
        console.error('Check your API key and provider configuration');
        break;
      
      case 'max_iterations':
        console.error('Turn exceeded maximum iterations');
        break;
      
      default:
        console.error('Turn failed:', event.data.error);
    }
  }
}

Complete examples

Build a chat UI

import { EverrunsClient } from 'everruns-sdk';
import readline from 'readline';

const client = new EverrunsClient({
  baseUrl: 'http://localhost:9300',
});

async function chat() {
  // Create session
  const agent = await client.agents.create({
    name: 'Chat Assistant',
    system_prompt: 'You are a helpful assistant.',
  });
  
  const session = await client.sessions.create({
    agent_id: agent.id,
  });

  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  // Send message and stream response
  async function sendMessage(text: string) {
    await client.messages.create(session.id, {
      message: {
        content: [{ type: 'text', text }]
      }
    });

    process.stdout.write('Agent: ');
    
    for await (const event of client.sessions.streamEvents(session.id)) {
      if (event.type === 'output.message.delta') {
        process.stdout.write(event.data.delta);
      }
      
      if (event.type === 'output.message.completed') {
        console.log('\n');
        askQuestion();
        break;
      }
      
      if (event.type === 'turn.failed') {
        console.error('\nError:', event.data.error);
        askQuestion();
        break;
      }
    }
  }

  function askQuestion() {
    rl.question('You: ', (answer) => {
      if (answer.toLowerCase() === 'exit') {
        rl.close();
        return;
      }
      sendMessage(answer);
    });
  }

  console.log('Chat started! Type "exit" to quit.\n');
  askQuestion();
}

chat().catch(console.error);

Monitor tool execution

async function monitorTools(sessionId: string) {
  const toolCalls = new Map<string, any>();

  for await (const event of client.sessions.streamEvents(sessionId)) {
    switch (event.type) {
      case 'tool.started':
        console.log(`[${event.data.tool_call.id}] Starting: ${event.data.tool_call.name}`);
        toolCalls.set(event.data.tool_call.id, {
          name: event.data.tool_call.name,
          started: Date.now(),
        });
        break;
      
      case 'tool.completed':
        const call = toolCalls.get(event.data.tool_call_id);
        const duration = Date.now() - call.started;
        
        console.log(`[${event.data.tool_call_id}] Completed: ${call.name}`);
        console.log(`  Duration: ${duration}ms`);
        console.log(`  Status: ${event.data.status}`);
        
        if (event.data.success) {
          console.log(`  Result: ${JSON.stringify(event.data.result)}`);
        } else {
          console.log(`  Error: ${event.data.error}`);
        }
        
        toolCalls.delete(event.data.tool_call_id);
        break;
    }
  }
}

Track performance metrics

async function trackMetrics(sessionId: string) {
  const metrics = {
    turns: 0,
    totalDuration: 0,
    totalInputTokens: 0,
    totalOutputTokens: 0,
    toolCalls: 0,
  };

  for await (const event of client.sessions.streamEvents(sessionId)) {
    switch (event.type) {
      case 'turn.completed':
        metrics.turns++;
        metrics.totalDuration += event.data.duration_ms;
        break;
      
      case 'llm.generation':
        if (event.data.metadata.usage) {
          metrics.totalInputTokens += event.data.metadata.usage.input_tokens;
          metrics.totalOutputTokens += event.data.metadata.usage.output_tokens;
        }
        break;
      
      case 'tool.completed':
        metrics.toolCalls++;
        break;
    }
    
    // Print metrics periodically
    if (event.type === 'session.idled') {
      console.log('Metrics:', {
        ...metrics,
        avgTurnDuration: metrics.totalDuration / metrics.turns,
      });
    }
  }
}

Best practices

Handle connection lifecycle

let reconnectAttempts = 0;
const MAX_RECONNECTS = 5;

async function streamWithRetry(sessionId: string) {
  while (reconnectAttempts < MAX_RECONNECTS) {
    try {
      for await (const event of client.sessions.streamEvents(sessionId)) {
        // Reset on successful connection
        reconnectAttempts = 0;
        
        // Process event
        console.log('Event:', event.type);
      }
    } catch (error) {
      reconnectAttempts++;
      console.error(`Connection lost. Retry ${reconnectAttempts}/${MAX_RECONNECTS}`);
      
      if (reconnectAttempts >= MAX_RECONNECTS) {
        throw error;
      }
      
      // Exponential backoff
      await new Promise(r => setTimeout(r, Math.min(1000 * Math.pow(2, reconnectAttempts), 30000)));
    }
  }
}

Use timeouts

async function streamWithTimeout(sessionId: string, timeoutMs: number) {
  const timeout = new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Stream timeout')), timeoutMs)
  );

  const stream = (async () => {
    for await (const event of client.sessions.streamEvents(sessionId)) {
      console.log('Event:', event.type);
      
      if (event.type === 'output.message.completed') {
        return;
      }
    }
  })();

  await Promise.race([stream, timeout]);
}

Clean event processing

// Create event handlers
const handlers = {
  'input.message': (event) => {
    console.log('User:', event.data.message.content[0]?.text);
  },
  'output.message.delta': (event) => {
    process.stdout.write(event.data.delta);
  },
  'output.message.completed': (event) => {
    console.log('\nAgent done');
  },
  'tool.started': (event) => {
    console.log('Tool:', event.data.tool_call.name);
  },
};

// Process events
for await (const event of client.sessions.streamEvents(sessionId)) {
  const handler = handlers[event.type];
  if (handler) {
    handler(event);
  }
}

Next steps

Messages

Learn about sending messages

API Reference

Explore the complete API