TypeScriptADK-TS
Events

Working with Events

Practical patterns and examples for handling events in ADK-TS applications

This guide provides practical patterns for handling events in your ADK-TS 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-TS.