@microsoft/agents-copilotstudio-client
Version:
Microsoft Copilot Studio Client for JavaScript. Copilot Studio Client.
331 lines (304 loc) • 12.5 kB
text/typescript
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { v4 as uuid } from 'uuid'
import { Activity, ConversationAccount } from '@microsoft/agents-activity'
import { Observable, BehaviorSubject, type Subscriber } from 'rxjs'
import { CopilotStudioClient } from './copilotStudioClient'
import { debug } from '@microsoft/agents-activity/logger'
const logger = debug('copilot-studio:webchat')
/**
* Configuration settings for the Copilot Studio WebChat connection.
* These settings control the behavior and appearance of the WebChat interface
* when connected to the Copilot Studio service.
*/
export interface CopilotStudioWebChatSettings {
/**
* Whether to show typing indicators in the WebChat when the agent is processing a response.
* When enabled, users will see a typing indicator while waiting for the agent's reply,
* providing visual feedback that their message is being processed.
* @default false
*/
showTyping?: boolean;
}
/**
* @summary Represents a connection interface for integrating Copilot Studio with WebChat.
* @remarks
* This interface provides the necessary methods and observables to facilitate
* bidirectional communication between a WebChat client and the Copilot Studio service.
*
* The connection follows the DirectLine protocol pattern, making it compatible with
* Microsoft Bot Framework WebChat components.
*/
export interface CopilotStudioWebChatConnection {
/**
* An observable that emits the current connection status as numeric values.
* This allows WebChat clients to monitor and react to connection state changes.
*
* Connection status values:
* - 0: Disconnected - No active connection to the service
* - 1: Connecting - Attempting to establish connection
* - 2: Connected - Successfully connected and ready for communication
*/
connectionStatus$: BehaviorSubject<number>;
/**
* An observable stream that emits incoming activities from the Copilot Studio service.
* Each activity represents a message, card, or other interactive element sent by the agent.
*
* All emitted activities include:
* - A timestamp indicating when the activity was received
* - A 'webchat:sequence-id' in their channelData for proper message ordering
* - Standard Bot Framework Activity properties (type, text, attachments, etc.)
*/
activity$: Observable<Partial<Activity>>;
/**
* Posts a user activity to the Copilot Studio service and returns an observable
* that emits the activity ID once the message is successfully sent.
*
* The method validates that the activity contains meaningful content and handles
* the complete message flow including optional typing indicators.
*
* @param activity - The user activity to send. Must contain a non-empty text field.
* @returns An observable that emits the unique activity ID upon successful posting.
* @throws Error if the activity text is empty or if the connection is not properly initialized.
*/
postActivity(activity: Activity): Observable<string>;
/**
* Gracefully terminates the connection to the Copilot Studio service.
* This method ensures proper cleanup by completing all active observables
* and releasing associated resources.
*
* After calling this method:
* - The connectionStatus$ observable will be completed
* - The activity$ observable will stop emitting new activities
* - No further activities can be posted through this connection
*/
end(): void;
}
/**
* @summary A utility class that provides WebChat integration capabilities for Copilot Studio services.
* @remarks
* This class acts as a bridge between Microsoft Bot Framework WebChat and Copilot Studio,
* enabling seamless communication through a DirectLine-compatible interface.
*
* ## Key Features:
* - DirectLine protocol compatibility for easy WebChat integration
* - Real-time bidirectional messaging with Copilot Studio agents
* - Automatic conversation management and message sequencing
* - Optional typing indicators for enhanced user experience
* - Observable-based architecture for reactive programming patterns
*
* ## Usage Scenarios:
* - Embedding Copilot Studio agents in web applications
* - Creating custom chat interfaces with WebChat components
* - Building conversational AI experiences with Microsoft's bot ecosystem
*
* @example Basic WebChat Integration
* ```typescript
* import { CopilotStudioClient } from '@microsoft/agents-copilotstudio-client';
* import { CopilotStudioWebChat } from '@microsoft/agents-copilotstudio-client';
*
* // Initialize the Copilot Studio client
* const client = new CopilotStudioClient({
* botId: 'your-bot-id',
* tenantId: 'your-tenant-id'
* });
*
* // Create a WebChat-compatible connection
* const directLine = CopilotStudioWebChat.createConnection(client, {
* showTyping: true
* });
*
* // Integrate with WebChat
* window.WebChat.renderWebChat({
* directLine: directLine,
* // ... other WebChat options
* }, document.getElementById('webchat'));
* ```
*
* @example Advanced Usage with Connection Monitoring
* ```typescript
* const connection = CopilotStudioWebChat.createConnection(client);
*
* // Monitor connection status
* connection.connectionStatus$.subscribe(status => {
* switch (status) {
* case 0: console.log('Disconnected'); break;
* case 1: console.log('Connecting...'); break;
* case 2: console.log('Connected and ready'); break;
* }
* });
*
* // Listen for incoming activities
* connection.activity$.subscribe(activity => {
* console.log('Received activity:', activity);
* });
* ```
*/
export class CopilotStudioWebChat {
/**
* Creates a DirectLine-compatible connection for integrating Copilot Studio with WebChat.
*
* This method establishes a real-time communication channel between WebChat and the
* Copilot Studio service. The returned connection object implements the DirectLine
* protocol, making it fully compatible with Microsoft Bot Framework WebChat components.
*
* ## Connection Lifecycle:
* 1. **Initialization**: Creates observables for connection status and activity streaming
* 2. **Conversation Start**: Automatically initiates conversation when first activity is posted
* 3. **Message Flow**: Handles bidirectional message exchange with proper sequencing
* 4. **Cleanup**: Provides graceful connection termination
*
* ## Message Processing:
* - User messages are validated and sent to Copilot Studio
* - Agent responses are received and formatted for WebChat
* - All activities include timestamps and sequence IDs for proper ordering
* - Optional typing indicators provide visual feedback during processing
*
* @param client - A configured CopilotStudioClient instance that handles the underlying
* communication with the Copilot Studio service. This client should be
* properly authenticated and configured with the target bot details.
*
* @param settings - Optional configuration settings that control the behavior of the
* WebChat connection. These settings allow customization of features
* like typing indicators and other user experience enhancements.
*
* @returns A new CopilotStudioWebChatConnection instance that can be passed directly
* to WebChat's renderWebChat function as the directLine parameter. The
* connection is immediately ready for use and will automatically manage
* the conversation lifecycle.
*
* @throws Error if the provided client is not properly configured or if there are
* issues establishing the initial connection to the Copilot Studio service.
*
* @example
* ```typescript
* const connection = CopilotStudioWebChat.createConnection(client, {
* showTyping: true
* });
*
* // Use with WebChat
* window.WebChat.renderWebChat({
* directLine: connection
* }, document.getElementById('webchat'));
* ```
*/
static createConnection (
client: CopilotStudioClient,
settings?: CopilotStudioWebChatSettings
):CopilotStudioWebChatConnection {
logger.info('--> Creating connection between Copilot Studio and WebChat ...')
let sequence = 0
let activitySubscriber: Subscriber<Partial<Activity>> | undefined
let conversation: ConversationAccount | undefined
const connectionStatus$ = new BehaviorSubject(0)
const activity$ = createObservable<Partial<Activity>>(async (subscriber) => {
activitySubscriber = subscriber
if (connectionStatus$.value < 2) {
connectionStatus$.next(2)
return
}
logger.debug('--> Connection established.')
notifyTyping()
const activity = await client.startConversationAsync()
conversation = activity.conversation
sequence = 0
notifyActivity(activity)
})
const notifyActivity = (activity: Partial<Activity>) => {
const newActivity = {
...activity,
timestamp: new Date().toISOString(),
channelData: {
...activity.channelData,
'webchat:sequence-id': sequence++,
},
}
logger.debug(`Notify '${newActivity.type}' activity to WebChat:`, newActivity)
activitySubscriber?.next(newActivity)
}
const notifyTyping = () => {
if (!settings?.showTyping) {
return
}
const from = conversation
? { id: conversation.id, name: conversation.name }
: { id: 'agent', name: 'Agent' }
notifyActivity({ type: 'typing', from })
}
return {
connectionStatus$,
activity$,
postActivity (activity: Activity) {
logger.info('--> Preparing to send activity to Copilot Studio ...')
if (!activity.text?.trim()) {
throw new Error('Activity text cannot be empty.')
}
if (!activitySubscriber) {
throw new Error('Activity subscriber is not initialized.')
}
return createObservable<string>(async (subscriber) => {
try {
const id = uuid()
logger.info('--> Sending activity to Copilot Studio ...')
notifyActivity({ ...activity, id })
notifyTyping()
const activities = await client.askQuestionAsync(activity.text!)
for (const responseActivity of activities) {
notifyActivity(responseActivity)
}
subscriber.next(id)
subscriber.complete()
logger.info('--> Activity received correctly from Copilot Studio.')
} catch (error) {
logger.error('Error sending Activity to Copilot Studio:', error)
subscriber.error(error)
}
})
},
end () {
logger.info('--> Ending connection between Copilot Studio and WebChat ...')
connectionStatus$.complete()
if (activitySubscriber) {
activitySubscriber.complete()
activitySubscriber = undefined
}
},
}
}
}
/**
* Creates an RxJS Observable that wraps an asynchronous function execution.
*
* This utility function provides a clean way to convert async/await patterns
* into Observable streams, enabling integration with reactive programming patterns
* used throughout the WebChat connection implementation.
*
* The created Observable handles promise resolution and rejection automatically,
* converting them to appropriate next/error signals for subscribers.
*
* @typeParam T - The type of value that the observable will emit
* @param fn - An asynchronous function that receives a Subscriber and performs
* the desired async operation. The function should call subscriber.next()
* with results and subscriber.complete() when finished.
* @returns A new Observable that executes the provided function and emits its results
*
* @example
* ```typescript
* const dataObservable = createObservable<string>(async (subscriber) => {
* try {
* const result = await fetchData();
* subscriber.next(result);
* subscriber.complete();
* } catch (error) {
* subscriber.error(error);
* }
* });
* ```
*/
function createObservable<T> (fn: (subscriber: Subscriber<T>) => void): Observable<T> {
return new Observable<T>((subscriber: Subscriber<T>) => {
Promise.resolve(fn(subscriber)).catch((error) => subscriber.error(error))
})
}