Skip to main content
The embed widget emits events at key points in the agent’s lifecycle, letting you react to user actions, track conversation flow, and handle errors in your application. There are two ways to listen to events: inline callbacks passed to embedWidget(), and the on() method on the widget instance.

Quick Start

import { embedWidget } from 'agent-embed-widget';
import 'agent-embed-widget/dist/agent-embed-widget.css';

const widget = embedWidget({
  url: 'https://console.thesys.dev/app/your-slug',

  // Inline callbacks
  onGenerationStarted: (threadId, messageId) => {
    console.log('Agent is thinking…', { threadId, messageId });
  },
  onGenerationEnded: (threadId, messageId) => {
    console.log('Agent replied', { threadId, messageId });
  },
});

// Or subscribe dynamically with on()
const unsub = widget.on('userMessageSent', (message, threadId) => {
  analytics.track('user_message', { message, threadId });
});

// Later: stop listening
unsub();

Event Reference

Thread Events

threadChanged

Fires when the active conversation thread changes — for example, when the user selects a different thread from the thread list, or a new thread is created after the first message is sent.
ParameterTypeDescription
threadIdstringThe ID of the newly active thread
widget.on('threadChanged', (threadId) => {
  console.log('Switched to thread:', threadId);
});

newThread

Fires when the user clicks “New Chat” to start a fresh conversation. At this point no thread has been created on the backend yet — the thread is created once the first message is sent (which then fires threadChanged).
widget.on('newThread', () => {
  console.log('User started a new chat');
});

Message Events

userMessageSent

Fires when the user sends a message to the agent.
ParameterTypeDescription
messagestringThe message content
threadIdstringThe thread the message was sent in
widget.on('userMessageSent', (message, threadId) => {
  console.log('User said:', message, 'in thread:', threadId);
});

generationStarted

Fires when the agent begins generating a response.
ParameterTypeDescription
threadIdstringThe thread the response is being generated in
messageIdstringThe ID of the assistant message being generated
widget.on('generationStarted', (threadId, messageId) => {
  showTypingIndicator(threadId);
});

generationEnded

Fires when the agent finishes generating a response. The messageId is the persisted backend ID that you can use with the Thesys API to fetch the message content.
ParameterTypeDescription
threadIdstringThe thread the response was generated in
messageIdstringThe backend ID of the completed assistant message
widget.on('generationEnded', (threadId, messageId) => {
  hideTypingIndicator(threadId);
  console.log('Response saved as:', messageId);
});

Tool Execution Events

These events fire when the agent uses a tool (web search, image search, artifact builder, etc.) during response generation. Internal tools used by the system are excluded — only user-facing tools trigger these events.

toolExecutionStarted

Fires when the agent starts executing a tool.
ParameterTypeDescription
toolNamestringThe name of the tool being executed
threadIdstringThe thread the tool is executing in
widget.on('toolExecutionStarted', (toolName, threadId) => {
  console.log(`Tool "${toolName}" started in thread ${threadId}`);
});

toolExecutionEnded

Fires when a tool finishes executing. If the tool failed, the error parameter contains the error message.
ParameterTypeDescription
toolNamestringThe name of the tool that finished
threadIdstringThe thread the tool executed in
errorstring | undefinedError message if the tool failed, undefined on success
widget.on('toolExecutionEnded', (toolName, threadId, error) => {
  if (error) {
    console.error(`Tool "${toolName}" failed:`, error);
  } else {
    console.log(`Tool "${toolName}" completed successfully`);
  }
});

Error Events

agentError

Fires when an error occurs during message processing or response streaming.
ParameterTypeDescription
codestringError code (MESSAGE_PROCESSING_ERROR or STREAM_PROCESSING_ERROR)
messagestringHuman-readable error description
widget.on('agentError', (code, message) => {
  errorReporter.capture({ code, message });
});

identityTokenError

Fires when a BYOI identity token is invalid or cannot be refreshed. This is separate from the normal token refresh flow — it indicates a persistent authentication failure.
ParameterTypeDescription
codestringError code identifying the failure type
messagestringHuman-readable error description
widget.on('identityTokenError', (code, message) => {
  console.error('Identity token error:', code, message);
  // Redirect user to re-authenticate
  window.location.href = '/login';
});

Inline Callbacks

All events can also be passed as callbacks directly to embedWidget(). Callback names use the on prefix with PascalCase:
const widget = embedWidget({
  url: 'https://console.thesys.dev/app/your-slug',
  onThreadChanged: (threadId) => { /* ... */ },
  onNewThread: () => { /* ... */ },
  onUserMessageSent: (message, threadId) => { /* ... */ },
  onGenerationStarted: (threadId, messageId) => { /* ... */ },
  onGenerationEnded: (threadId, messageId) => { /* ... */ },
  onAgentError: (code, message) => { /* ... */ },
  onToolExecutionStarted: (toolName, threadId) => { /* ... */ },
  onToolExecutionEnded: (toolName, threadId, error) => { /* ... */ },
  onIdentityTokenError: (code, message) => { /* ... */ },
});
Both inline callbacks and on() listeners fire for the same event — you can use either or both.

widget.on(event, callback)

Subscribe to events dynamically after the widget is created. Returns an unsubscribe function.
const unsubscribe = widget.on('generationEnded', (threadId, messageId) => {
  console.log('Done:', messageId);
});

// Stop listening
unsubscribe();
Multiple listeners can be registered for the same event. Each call to on() returns its own independent unsubscribe function.

Typical Event Flow

Here’s the sequence of events during a normal conversation turn:
newThread                          // User clicks "New Chat"
  → userMessageSent               // User sends first message
  → threadChanged (threadId)      // Backend creates the thread
  → generationStarted             // Agent begins responding
    → toolExecutionStarted        // (if the agent uses a tool)
    → toolExecutionEnded          //
  → generationEnded               // Agent finishes responding
For subsequent messages in the same thread:
userMessageSent
  → generationStarted
  → generationEnded

Direct Iframe Integration

If you’re embedding the agent via a direct iframe instead of the embed widget npm package, you can listen for the same events using the browser’s postMessage API. Each event is sent as a message with a type field prefixed with THESYS_.
For full details on postMessage usage, origin validation, and URL parameters, see the PostMessage Protocol page.

Event Mapping

Embed Widget EventpostMessage typePayload
threadChangedTHESYS_THREAD_CHANGED{ threadId: string }
newThreadTHESYS_NEW_THREAD(none)
userMessageSentTHESYS_USER_MESSAGE_SENT{ message: string, threadId: string }
generationStartedTHESYS_GENERATION_STARTED{ threadId: string, messageId: string }
generationEndedTHESYS_GENERATION_ENDED{ threadId: string, messageId: string }
toolExecutionStartedTHESYS_TOOL_EXECUTION_STARTED{ toolName: string, threadId: string }
toolExecutionEndedTHESYS_TOOL_EXECUTION_ENDED{ toolName: string, threadId: string, error?: string }
agentErrorTHESYS_AGENT_ERROR{ code: string, message: string }
identityTokenErrorTHESYS_IDENTITY_TOKEN_ERROR{ code: string, message: string }
(widget-only)THESYS_APP_READY(none) — the agent has finished loading
(widget-only)THESYS_WIDGET_CLOSE(none) — the agent requested to close
(widget-only)THESYS_WIDGET_OPEN(none) — the agent requested to open
(widget-only)THESYS_WIDGET_TOGGLE(none) — the agent requested to toggle visibility
(widget-only)THESYS_IDENTITY_TOKEN_REFRESH_NEEDED(none) — token expired, parent should refresh

Example

const iframe = document.getElementById('thesys-agent');
const expectedOrigin = new URL(iframe.src).origin;

window.addEventListener('message', (event) => {
  if (event.origin !== expectedOrigin) return;
  if (!event.data?.type) return;

  switch (event.data.type) {
    case 'THESYS_APP_READY':
      console.log('Agent loaded');
      break;
    case 'THESYS_THREAD_CHANGED':
      console.log('Thread:', event.data.threadId);
      break;
    case 'THESYS_NEW_THREAD':
      console.log('New chat started');
      break;
    case 'THESYS_USER_MESSAGE_SENT':
      console.log('User said:', event.data.message);
      break;
    case 'THESYS_GENERATION_STARTED':
      console.log('Generating…', event.data.messageId);
      break;
    case 'THESYS_GENERATION_ENDED':
      console.log('Done:', event.data.messageId);
      break;
    case 'THESYS_TOOL_EXECUTION_STARTED':
      console.log('Tool started:', event.data.toolName);
      break;
    case 'THESYS_TOOL_EXECUTION_ENDED':
      console.log('Tool ended:', event.data.toolName, event.data.error);
      break;
    case 'THESYS_AGENT_ERROR':
      console.error('Error:', event.data.code, event.data.message);
      break;
    case 'THESYS_IDENTITY_TOKEN_ERROR':
      console.error('Token error:', event.data.code, event.data.message);
      break;
    case 'THESYS_IDENTITY_TOKEN_REFRESH_NEEDED':
      // Fetch a new token and send THESYS_IDENTITY_TOKEN_REFRESHED back
      break;
  }
});

Example: Analytics Integration

import { embedWidget } from 'agent-embed-widget';
import 'agent-embed-widget/dist/agent-embed-widget.css';

const widget = embedWidget({
  url: 'https://console.thesys.dev/app/your-slug',
});

widget.on('userMessageSent', (message, threadId) => {
  analytics.track('agent_message_sent', {
    threadId,
    messageLength: message.length,
  });
});

widget.on('generationEnded', (threadId, messageId) => {
  analytics.track('agent_response_received', {
    threadId,
    messageId,
  });
});

widget.on('toolExecutionStarted', (toolName, threadId) => {
  analytics.track('agent_tool_used', { toolName, threadId });
});

widget.on('agentError', (code, message) => {
  analytics.track('agent_error', { code, message });
});