TypeScriptADK-TS
Events

Working with Events

Practical patterns and examples for handling events in ADK applications

This guide provides practical patterns for handling events in your ADK applications, from basic event processing to advanced patterns for complex workflows.

Event Processing Basics

Event Identification

First, understand what type of event you're dealing with:

import { Event } from '@iqai/adk';

function processEvent(event: Event) {
  console.log(`Event from: ${event.author}`);
  console.log(`Event ID: ${event.id}`);
  console.log(`Invocation ID: ${event.invocationId}`);

  // Check event type
  if (event.content?.parts) {
    const functionCalls = event.getFunctionCalls();
    const functionResponses = event.getFunctionResponses();

    if (functionCalls.length > 0) {
      console.log("Type: Tool Call Request");
      functionCalls.forEach(call => {
        console.log(`  Tool: ${call.name}`, call.args);
      });
    } else if (functionResponses.length > 0) {
      console.log("Type: Tool Result");
      functionResponses.forEach(response => {
        console.log(`  Result from: ${response.name}`, response.response);
      });
    } else if (event.content.parts[0]?.text) {
      console.log(event.partial ? "Type: Streaming Text" : "Type: Complete Text");
    }
  }
}

Extracting Content

Access different types of content from events:

function extractText(event: Event): string | null {
  if (event.content?.parts?.[0]?.text) {
    return event.content.parts[0].text;
  }
  return null;
}

// Usage
const text = extractText(event);
if (text) {
  console.log("Message:", text);
}
function processFunctionCalls(event: Event) {
  const calls = event.getFunctionCalls();

  for (const call of calls) {
    console.log(`Tool requested: ${call.name}`);
    console.log(`Arguments:`, call.args);

    // Dispatch based on tool name
    switch (call.name) {
      case 'search':
        handleSearchRequest(call.args);
        break;
      case 'sendEmail':
        handleEmailRequest(call.args);
        break;
      default:
        console.log(`Unknown tool: ${call.name}`);
    }
  }
}
function processFunctionResponses(event: Event) {
  const responses = event.getFunctionResponses();

  for (const response of responses) {
    console.log(`Tool ${response.name} returned:`, response.response);

    // Handle specific tool responses
    if (response.name === 'search') {
      displaySearchResults(response.response);
    } else if (response.name === 'sendEmail') {
      showEmailConfirmation(response.response);
    }
  }
}

Event Filtering Patterns

Final Response Detection

Use the built-in helper to identify display-ready events:

async function processAgentConversation(runner, query, session) {
  let streamingText = '';

  for await (const event of runner.runAsync(query, session)) {
    // Handle streaming content
    if (event.partial && event.content?.parts?.[0]?.text) {
      streamingText += event.content.parts[0].text;
      updateUI(streamingText); // Real-time updates
    }

    // Handle final responses
    if (event.isFinalResponse()) {
      if (event.content?.parts?.[0]?.text) {
        // Complete text response
        const finalText = streamingText + (event.content.parts[0].text || '');
        displayFinalMessage(finalText.trim());
        streamingText = ''; // Reset
      } else if (event.actions?.skipSummarization) {
        // Raw tool result
        const toolResponse = event.getFunctionResponses()[0];
        displayToolResult(toolResponse.response);
      } else if (event.longRunningToolIds) {
        // Background process
        showProcessingIndicator();
      }
    }

    // Handle actions
    processEventActions(event);
  }
}

Custom Event Filters

Create filters for specific event types:

// Filter by author
const agentEvents = events.filter(event => event.author !== 'user');
const userEvents = events.filter(event => event.author === 'user');

// Filter by content type
const textEvents = events.filter(event =>
  event.content?.parts?.[0]?.text && !event.partial
);

const toolEvents = events.filter(event =>
  event.getFunctionCalls().length > 0 ||
  event.getFunctionResponses().length > 0
);

// Filter by time range
const recentEvents = events.filter(event =>
  event.timestamp > (Date.now() / 1000) - 3600 // Last hour
);

State and Action Handling

Processing State Changes

Monitor and react to state updates:

function handleStateChanges(event: Event) {
  if (!event.actions?.stateDelta) return;

  const changes = event.actions.stateDelta;

  for (const [key, value] of Object.entries(changes)) {
    console.log(`State changed: ${key} = ${value}`);

    // React to specific state changes
    switch (key) {
      case 'user_preference':
        updateUserInterface(value);
        break;
      case 'task_status':
        updateTaskProgress(value);
        break;
      case 'app:theme':
        applyTheme(value);
        break;
    }
  }
}

Artifact Tracking

Track file operations:

function handleArtifactChanges(event: Event) {
  if (!event.actions?.artifactDelta) return;

  const artifacts = event.actions.artifactDelta;

  for (const [filename, version] of Object.entries(artifacts)) {
    console.log(`File updated: ${filename} (version ${version})`);

    // Refresh file displays
    if (filename.endsWith('.pdf')) {
      refreshPdfViewer(filename);
    } else if (filename.endsWith('.json')) {
      refreshDataView(filename);
    }
  }
}

Advanced Event Patterns

Event Aggregation

Combine related events for analysis:

class EventAggregator {
  private events: Event[] = [];

  addEvent(event: Event) {
    this.events.push(event);
  }

  getConversationSummary(invocationId: string) {
    const conversationEvents = this.events.filter(
      event => event.invocationId === invocationId
    );

    return {
      totalEvents: conversationEvents.length,
      userMessages: conversationEvents.filter(e => e.author === 'user').length,
      agentResponses: conversationEvents.filter(e => e.author !== 'user').length,
      toolCalls: conversationEvents.reduce(
        (sum, e) => sum + e.getFunctionCalls().length, 0
      ),
      duration: this.getConversationDuration(conversationEvents)
    };
  }

  private getConversationDuration(events: Event[]): number {
    if (events.length < 2) return 0;
    const sorted = events.sort((a, b) => a.timestamp - b.timestamp);
    return sorted[sorted.length - 1].timestamp - sorted[0].timestamp;
  }
}

Event Replay

Recreate conversation state from event history:

function replayEvents(events: Event[]): Record<string, any> {
  const state: Record<string, any> = {};

  for (const event of events.sort((a, b) => a.timestamp - b.timestamp)) {
    if (event.actions?.stateDelta) {
      for (const [key, value] of Object.entries(event.actions.stateDelta)) {
        if (!key.startsWith('temp_')) {
          state[key] = value;
        }
      }
    }
  }

  return state;
}

Error Event Handling

Handle errors gracefully:

function processEventSafely(event: Event) {
  try {
    // Check for error conditions
    if (event.errorCode) {
      handleEventError(event);
      return;
    }

    // Normal processing
    processEvent(event);

  } catch (error) {
    console.error('Error processing event:', error);
    logEventError(event, error);
  }
}

function handleEventError(event: Event) {
  console.error(`Event error: ${event.errorCode}`);
  console.error(`Message: ${event.errorMessage}`);

  // Show user-friendly error message
  switch (event.errorCode) {
    case 'SAFETY_FILTER_TRIGGERED':
      showSafetyWarning();
      break;
    case 'RATE_LIMIT_EXCEEDED':
      showRateLimitMessage();
      break;
    default:
      showGenericError(event.errorMessage);
  }
}

Building Event-Driven UIs

React Integration

import { useState, useEffect } from 'react';

function ConversationComponent({ runner, query, session }) {
  const [events, setEvents] = useState<Event[]>([]);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    async function runConversation() {
      setIsLoading(true);
      const newEvents: Event[] = [];

      try {
        for await (const event of runner.runAsync(query, session)) {
          newEvents.push(event);
          setEvents([...newEvents]); // Update UI with each event
        }
      } finally {
        setIsLoading(false);
      }
    }

    runConversation();
  }, [query]);

  return (
    <div>
      {events.map(event => (
        <EventDisplay key={event.id} event={event} />
      ))}
      {isLoading && <LoadingIndicator />}
    </div>
  );
}

Best Practices

Performance

  • Filter early: Apply filters before processing heavy operations
  • Batch updates: Group related state changes when possible
  • Cleanup: Remove event listeners and clear large event arrays

Error Handling

  • Graceful degradation: Continue processing other events if one fails
  • User feedback: Provide clear error messages for failed operations
  • Logging: Record detailed error information for debugging

Memory Management

  • Event retention: Only keep necessary events in memory
  • Pagination: Use pagination for large event histories
  • Cleanup: Clear temporary state and cached data

Events provide the foundation for building responsive, interactive agent applications with ADK.