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.