@microsoft/agents-copilotstudio-client 0.6.1 → 0.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,106 +6,229 @@
6
6
  import { v4 as uuid } from 'uuid'
7
7
 
8
8
  import { Activity, ConversationAccount } from '@microsoft/agents-activity'
9
- import { Observable, BehaviorSubject, type Observer } from 'rxjs'
9
+ import { Observable, BehaviorSubject, type Subscriber } from 'rxjs'
10
10
 
11
11
  import { CopilotStudioClient } from './copilotStudioClient'
12
+ import { debug } from '@microsoft/agents-activity/src/logger'
12
13
 
14
+ const logger = debug('copilot-studio:webchat')
15
+
16
+ /**
17
+ * Configuration settings for the Copilot Studio WebChat connection.
18
+ * These settings control the behavior and appearance of the WebChat interface
19
+ * when connected to the Copilot Studio service.
20
+ */
13
21
  export interface CopilotStudioWebChatSettings {
14
22
  /**
15
- * Whether to show typing indicators in the WebChat.
16
- * Defaults to false.
23
+ * Whether to show typing indicators in the WebChat when the agent is processing a response.
24
+ * When enabled, users will see a typing indicator while waiting for the agent's reply,
25
+ * providing visual feedback that their message is being processed.
26
+ * @default false
17
27
  */
18
28
  showTyping?: boolean;
19
29
  }
20
30
 
31
+ /**
32
+ * Represents a connection interface for integrating Copilot Studio with WebChat.
33
+ * This interface provides the necessary methods and observables to facilitate
34
+ * bidirectional communication between a WebChat client and the Copilot Studio service.
35
+ *
36
+ * The connection follows the DirectLine protocol pattern, making it compatible with
37
+ * Microsoft Bot Framework WebChat components.
38
+ */
21
39
  export interface CopilotStudioWebChatConnection {
22
40
  /**
23
- * An observable that emits the connection status.
24
- * 0 - Disconnected
25
- * 1 - Connecting
26
- * 2 - Connected
27
- */
41
+ * An observable that emits the current connection status as numeric values.
42
+ * This allows WebChat clients to monitor and react to connection state changes.
43
+ *
44
+ * Connection status values:
45
+ * - 0: Disconnected - No active connection to the service
46
+ * - 1: Connecting - Attempting to establish connection
47
+ * - 2: Connected - Successfully connected and ready for communication
48
+ */
28
49
  connectionStatus$: BehaviorSubject<number>;
29
50
 
30
51
  /**
31
- * An observable that emits incoming activities.
32
- * The emitted activities will have a 'webchat:sequence-id' in their channelData.
52
+ * An observable stream that emits incoming activities from the Copilot Studio service.
53
+ * Each activity represents a message, card, or other interactive element sent by the agent.
54
+ *
55
+ * All emitted activities include:
56
+ * - A timestamp indicating when the activity was received
57
+ * - A 'webchat:sequence-id' in their channelData for proper message ordering
58
+ * - Standard Bot Framework Activity properties (type, text, attachments, etc.)
33
59
  */
34
60
  activity$: Observable<Partial<Activity>>;
35
61
 
36
62
  /**
37
- * Posts an activity to the Copilot Studio service.
38
- * The activity must have a non-empty text field.
39
- * Returns an observable that emits the activity ID once the activity is posted.
63
+ * Posts a user activity to the Copilot Studio service and returns an observable
64
+ * that emits the activity ID once the message is successfully sent.
40
65
  *
41
- * @param activity - The activity to post.
42
- * @returns An observable that emits the activity ID.
66
+ * The method validates that the activity contains meaningful content and handles
67
+ * the complete message flow including optional typing indicators.
68
+ *
69
+ * @param activity - The user activity to send. Must contain a non-empty text field.
70
+ * @returns An observable that emits the unique activity ID upon successful posting.
71
+ * @throws Error if the activity text is empty or if the connection is not properly initialized.
43
72
  */
44
73
  postActivity(activity: Activity): Observable<string>;
45
74
 
46
75
  /**
47
- * Ends the connection.
48
- * This will complete the connectionStatus$ and activity$ observables.
76
+ * Gracefully terminates the connection to the Copilot Studio service.
77
+ * This method ensures proper cleanup by completing all active observables
78
+ * and releasing associated resources.
79
+ *
80
+ * After calling this method:
81
+ * - The connectionStatus$ observable will be completed
82
+ * - The activity$ observable will stop emitting new activities
83
+ * - No further activities can be posted through this connection
49
84
  */
50
85
  end(): void;
51
86
  }
52
87
 
53
88
  /**
54
- * This class is intended to be used in WebChat applications to connect to the Copilot Studio service.
89
+ * A utility class that provides WebChat integration capabilities for Copilot Studio services.
90
+ * This class acts as a bridge between Microsoft Bot Framework WebChat and Copilot Studio,
91
+ * enabling seamless communication through a DirectLine-compatible interface.
55
92
  *
56
- * example usage:
57
- * ```javascript
58
- * const client = new CopilotStudioClient(...)
93
+ * ## Key Features:
94
+ * - DirectLine protocol compatibility for easy WebChat integration
95
+ * - Real-time bidirectional messaging with Copilot Studio agents
96
+ * - Automatic conversation management and message sequencing
97
+ * - Optional typing indicators for enhanced user experience
98
+ * - Observable-based architecture for reactive programming patterns
99
+ *
100
+ * ## Usage Scenarios:
101
+ * - Embedding Copilot Studio agents in web applications
102
+ * - Creating custom chat interfaces with WebChat components
103
+ * - Building conversational AI experiences with Microsoft's bot ecosystem
104
+ *
105
+ * @example Basic WebChat Integration
106
+ * ```typescript
107
+ * import { CopilotStudioClient } from '@microsoft/agents-copilotstudio-client';
108
+ * import { CopilotStudioWebChat } from '@microsoft/agents-copilotstudio-client';
109
+ *
110
+ * // Initialize the Copilot Studio client
111
+ * const client = new CopilotStudioClient({
112
+ * botId: 'your-bot-id',
113
+ * tenantId: 'your-tenant-id'
114
+ * });
115
+ *
116
+ * // Create a WebChat-compatible connection
117
+ * const directLine = CopilotStudioWebChat.createConnection(client, {
118
+ * showTyping: true
119
+ * });
120
+ *
121
+ * // Integrate with WebChat
59
122
  * window.WebChat.renderWebChat({
60
- * directLine: CopilotStudioWebChat.createConnection(client)
61
- * })
123
+ * directLine: directLine,
124
+ * // ... other WebChat options
125
+ * }, document.getElementById('webchat'));
126
+ * ```
127
+ *
128
+ * @example Advanced Usage with Connection Monitoring
129
+ * ```typescript
130
+ * const connection = CopilotStudioWebChat.createConnection(client);
131
+ *
132
+ * // Monitor connection status
133
+ * connection.connectionStatus$.subscribe(status => {
134
+ * switch (status) {
135
+ * case 0: console.log('Disconnected'); break;
136
+ * case 1: console.log('Connecting...'); break;
137
+ * case 2: console.log('Connected and ready'); break;
138
+ * }
139
+ * });
140
+ *
141
+ * // Listen for incoming activities
142
+ * connection.activity$.subscribe(activity => {
143
+ * console.log('Received activity:', activity);
144
+ * });
62
145
  * ```
63
146
  */
64
147
  export class CopilotStudioWebChat {
65
148
  /**
66
- * Creates a new DirectLine-Like connection to WebChat.
67
- * When an activity is posted in WebChat, the connection will send it to the Copilot Studio service, awaiting response.
68
- * After a response is received, it will emit the incoming activity back to WebChat.
149
+ * Creates a DirectLine-compatible connection for integrating Copilot Studio with WebChat.
150
+ *
151
+ * This method establishes a real-time communication channel between WebChat and the
152
+ * Copilot Studio service. The returned connection object implements the DirectLine
153
+ * protocol, making it fully compatible with Microsoft Bot Framework WebChat components.
154
+ *
155
+ * ## Connection Lifecycle:
156
+ * 1. **Initialization**: Creates observables for connection status and activity streaming
157
+ * 2. **Conversation Start**: Automatically initiates conversation when first activity is posted
158
+ * 3. **Message Flow**: Handles bidirectional message exchange with proper sequencing
159
+ * 4. **Cleanup**: Provides graceful connection termination
69
160
  *
70
- * @param client - The Copilot Studio client instance.
71
- * @param settings - Optional settings for the WebChat connection.
72
- * @returns A new instance of CopilotStudioWebChatConnection.
161
+ * ## Message Processing:
162
+ * - User messages are validated and sent to Copilot Studio
163
+ * - Agent responses are received and formatted for WebChat
164
+ * - All activities include timestamps and sequence IDs for proper ordering
165
+ * - Optional typing indicators provide visual feedback during processing
166
+ *
167
+ * @param client - A configured CopilotStudioClient instance that handles the underlying
168
+ * communication with the Copilot Studio service. This client should be
169
+ * properly authenticated and configured with the target bot details.
170
+ *
171
+ * @param settings - Optional configuration settings that control the behavior of the
172
+ * WebChat connection. These settings allow customization of features
173
+ * like typing indicators and other user experience enhancements.
174
+ *
175
+ * @returns A new CopilotStudioWebChatConnection instance that can be passed directly
176
+ * to WebChat's renderWebChat function as the directLine parameter. The
177
+ * connection is immediately ready for use and will automatically manage
178
+ * the conversation lifecycle.
179
+ *
180
+ * @throws Error if the provided client is not properly configured or if there are
181
+ * issues establishing the initial connection to the Copilot Studio service.
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * const connection = CopilotStudioWebChat.createConnection(client, {
186
+ * showTyping: true
187
+ * });
188
+ *
189
+ * // Use with WebChat
190
+ * window.WebChat.renderWebChat({
191
+ * directLine: connection
192
+ * }, document.getElementById('webchat'));
193
+ * ```
73
194
  */
74
195
  static createConnection (
75
196
  client: CopilotStudioClient,
76
197
  settings?: CopilotStudioWebChatSettings
77
198
  ):CopilotStudioWebChatConnection {
199
+ logger.info('--> Creating connection between Copilot Studio and WebChat ...')
78
200
  let sequence = 0
79
- let activityObserver: Observer<Partial<Activity>> | undefined
201
+ let activitySubscriber: Subscriber<Partial<Activity>> | undefined
80
202
  let conversation: ConversationAccount | undefined
81
203
 
82
204
  const connectionStatus$ = new BehaviorSubject(0)
83
- const activity$ = Observable.create(
84
- async (observer: Observer<Partial<Activity>>) => {
85
- activityObserver = observer
205
+ const activity$ = createObservable<Partial<Activity>>(async (subscriber) => {
206
+ activitySubscriber = subscriber
86
207
 
87
- if (connectionStatus$.value < 2) {
88
- connectionStatus$.next(2)
89
- return
90
- }
91
-
92
- notifyTyping()
93
- const activity = await client.startConversationAsync()
94
- conversation = activity.conversation
95
- sequence = 0
96
- notifyActivity(activity)
208
+ if (connectionStatus$.value < 2) {
209
+ connectionStatus$.next(2)
210
+ return
97
211
  }
98
- )
212
+
213
+ logger.debug('--> Connection established.')
214
+ notifyTyping()
215
+ const activity = await client.startConversationAsync()
216
+ conversation = activity.conversation
217
+ sequence = 0
218
+ notifyActivity(activity)
219
+ })
99
220
 
100
221
  const notifyActivity = (activity: Partial<Activity>) => {
101
- activityObserver?.next({
222
+ const newActivity = {
102
223
  ...activity,
103
224
  timestamp: new Date().toISOString(),
104
225
  channelData: {
105
226
  ...activity.channelData,
106
227
  'webchat:sequence-id': sequence++,
107
228
  },
108
- })
229
+ }
230
+ logger.debug(`Notify '${newActivity.type}' activity to WebChat:`, newActivity)
231
+ activitySubscriber?.next(newActivity)
109
232
  }
110
233
 
111
234
  const notifyTyping = () => {
@@ -123,18 +246,22 @@ export class CopilotStudioWebChat {
123
246
  connectionStatus$,
124
247
  activity$,
125
248
  postActivity (activity: Activity) {
249
+ logger.info('--> Preparing to send activity to Copilot Studio ...')
250
+
126
251
  if (!activity.text?.trim()) {
127
252
  throw new Error('Activity text cannot be empty.')
128
253
  }
129
254
 
130
- if (!activityObserver) {
131
- throw new Error('Activity observer is not initialized.')
255
+ if (!activitySubscriber) {
256
+ throw new Error('Activity subscriber is not initialized.')
132
257
  }
133
258
 
134
- return Observable.create(async (observer: Observer<string>) => {
259
+ return createObservable<string>(async (subscriber) => {
135
260
  try {
136
261
  const id = uuid()
137
262
 
263
+ logger.info('--> Sending activity to Copilot Studio ...')
264
+
138
265
  notifyActivity({ ...activity, id })
139
266
  notifyTyping()
140
267
 
@@ -143,22 +270,59 @@ export class CopilotStudioWebChat {
143
270
  notifyActivity(responseActivity)
144
271
  }
145
272
 
146
- observer.next(id)
147
- observer.complete()
273
+ subscriber.next(id)
274
+ subscriber.complete()
275
+ logger.info('--> Activity received correctly from Copilot Studio.')
148
276
  } catch (error) {
149
- observer.error(error)
277
+ logger.error('Error sending Activity to Copilot Studio:', error)
278
+ subscriber.error(error)
150
279
  }
151
280
  })
152
281
  },
153
282
 
154
283
  end () {
284
+ logger.info('--> Ending connection between Copilot Studio and WebChat ...')
155
285
  connectionStatus$.complete()
156
- activity$.complete()
157
- if (activityObserver) {
158
- activityObserver.complete()
159
- activityObserver = undefined
286
+ if (activitySubscriber) {
287
+ activitySubscriber.complete()
288
+ activitySubscriber = undefined
160
289
  }
161
290
  },
162
291
  }
163
292
  }
164
293
  }
294
+
295
+ /**
296
+ * Creates an RxJS Observable that wraps an asynchronous function execution.
297
+ *
298
+ * This utility function provides a clean way to convert async/await patterns
299
+ * into Observable streams, enabling integration with reactive programming patterns
300
+ * used throughout the WebChat connection implementation.
301
+ *
302
+ * The created Observable handles promise resolution and rejection automatically,
303
+ * converting them to appropriate next/error signals for subscribers.
304
+ *
305
+ * @template T - The type of value that the observable will emit
306
+ * @param fn - An asynchronous function that receives a Subscriber and performs
307
+ * the desired async operation. The function should call subscriber.next()
308
+ * with results and subscriber.complete() when finished.
309
+ * @returns A new Observable that executes the provided function and emits its results
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * const dataObservable = createObservable<string>(async (subscriber) => {
314
+ * try {
315
+ * const result = await fetchData();
316
+ * subscriber.next(result);
317
+ * subscriber.complete();
318
+ * } catch (error) {
319
+ * subscriber.error(error);
320
+ * }
321
+ * });
322
+ * ```
323
+ */
324
+ function createObservable<T> (fn: (subscriber: Subscriber<T>) => void): Observable<T> {
325
+ return new Observable<T>((subscriber: Subscriber<T>) => {
326
+ Promise.resolve(fn(subscriber)).catch((error) => subscriber.error(error))
327
+ })
328
+ }
@@ -5,10 +5,13 @@
5
5
 
6
6
  import { AgentType } from './agentType'
7
7
  import { ConnectionSettings } from './connectionSettings'
8
+ import { debug } from '@microsoft/agents-activity/src/logger'
8
9
  import { PowerPlatformCloud } from './powerPlatformCloud'
9
10
  import { PrebuiltBotStrategy } from './strategies/prebuiltBotStrategy'
10
11
  import { PublishedBotStrategy } from './strategies/publishedBotStrategy'
11
12
 
13
+ const logger = debug('copilot-studio:power-platform')
14
+
12
15
  /**
13
16
  * Generates the connection URL for Copilot Studio.
14
17
  * @param settings - The connection settings.
@@ -23,12 +26,14 @@ export function getCopilotStudioConnectionUrl (
23
26
  let cloudValue: PowerPlatformCloud = PowerPlatformCloud.Prod
24
27
 
25
28
  if (settings.directConnectUrl?.trim()) {
29
+ logger.debug(`Using direct connection: ${settings.directConnectUrl}`)
26
30
  if (!isValidUri(settings.directConnectUrl)) {
27
31
  throw new Error('directConnectUrl must be a valid URL')
28
32
  }
29
33
 
30
34
  // FIX for Missing Tenant ID
31
- if (settings.directConnectUrl.toLocaleLowerCase().includes('tenants/00000000-0000-0000-0000-000000000000')) {
35
+ if (settings.directConnectUrl.toLowerCase().includes('tenants/00000000-0000-0000-0000-000000000000')) {
36
+ logger.debug(`Direct connection cannot be used, forcing default settings flow. Tenant ID is missing in the URL: ${settings.directConnectUrl}`)
32
37
  // Direct connection cannot be used, ejecting and forcing the normal settings flow:
33
38
  return getCopilotStudioConnectionUrl({ ...settings, directConnectUrl: '' }, conversationId)
34
39
  }
@@ -58,15 +63,17 @@ export function getCopilotStudioConnectionUrl (
58
63
  }
59
64
 
60
65
  if (cloudSetting !== PowerPlatformCloud.Unknown) {
66
+ logger.debug(`Using specified cloud setting: ${cloudSetting}`)
61
67
  cloudValue = cloudSetting
62
68
  }
63
69
 
64
70
  if (cloudSetting === PowerPlatformCloud.Other) {
65
71
  if (isNotEmptyCustomPowerPlatformCloud && isValidUri(settings.customPowerPlatformCloud!)) {
72
+ logger.debug(`Using custom Power Platform cloud: ${settings.customPowerPlatformCloud}`)
66
73
  cloudValue = PowerPlatformCloud.Other
67
74
  } else {
68
75
  throw new Error(
69
- 'customPowerPlatformCloud must be provided when PowerPlatformCloud is Other'
76
+ 'customPowerPlatformCloud must be a valid URL'
70
77
  )
71
78
  }
72
79
  }
@@ -77,9 +84,11 @@ export function getCopilotStudioConnectionUrl (
77
84
  if (!Object.values(AgentType).includes(settings.copilotAgentType)) {
78
85
  throw new Error('Invalid AgentType enum key')
79
86
  } else {
87
+ logger.debug(`Using specified agent type: ${settings.copilotAgentType}`)
80
88
  agentType = settings.copilotAgentType
81
89
  }
82
90
  } else {
91
+ logger.debug('Using default agent type: Published')
83
92
  agentType = AgentType.Published
84
93
  }
85
94
 
@@ -98,7 +107,9 @@ export function getCopilotStudioConnectionUrl (
98
107
  }),
99
108
  }[agentType]()
100
109
 
101
- return strategy.getConversationUrl(conversationId)
110
+ const url = strategy.getConversationUrl(conversationId)
111
+ logger.debug(`Generated Copilot Studio connection URL: ${url}`)
112
+ return url
102
113
  }
103
114
 
104
115
  /**