@microsoft/agents-copilotstudio-client 1.4.0-beta.6.g725ba9fc33 → 1.4.1

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.
Files changed (54) hide show
  1. package/dist/package.json +2 -2
  2. package/dist/src/browser.mjs +5 -5
  3. package/dist/src/browser.mjs.map +4 -4
  4. package/dist/src/connectionSettings.d.ts +2 -0
  5. package/dist/src/connectionSettings.js +5 -2
  6. package/dist/src/connectionSettings.js.map +1 -1
  7. package/dist/src/copilotStudioClient.d.ts +61 -6
  8. package/dist/src/copilotStudioClient.js +176 -38
  9. package/dist/src/copilotStudioClient.js.map +1 -1
  10. package/dist/src/copilotStudioConnectionSettings.d.ts +6 -0
  11. package/dist/src/copilotStudioWebChat.d.ts +30 -0
  12. package/dist/src/copilotStudioWebChat.js +38 -11
  13. package/dist/src/copilotStudioWebChat.js.map +1 -1
  14. package/dist/src/executeTurnRequest.d.ts +5 -2
  15. package/dist/src/executeTurnRequest.js +4 -2
  16. package/dist/src/executeTurnRequest.js.map +1 -1
  17. package/dist/src/index.d.ts +5 -0
  18. package/dist/src/index.js +5 -0
  19. package/dist/src/index.js.map +1 -1
  20. package/dist/src/powerPlatformEnvironment.d.ts +8 -0
  21. package/dist/src/powerPlatformEnvironment.js +21 -6
  22. package/dist/src/powerPlatformEnvironment.js.map +1 -1
  23. package/dist/src/responses.d.ts +50 -0
  24. package/dist/src/responses.js +35 -0
  25. package/dist/src/responses.js.map +1 -0
  26. package/dist/src/scopeHelper.d.ts +17 -0
  27. package/dist/src/scopeHelper.js +24 -0
  28. package/dist/src/scopeHelper.js.map +1 -0
  29. package/dist/src/startRequest.d.ts +34 -0
  30. package/dist/src/startRequest.js +22 -0
  31. package/dist/src/startRequest.js.map +1 -0
  32. package/dist/src/strategies/prebuiltBotStrategy.d.ts +2 -1
  33. package/dist/src/strategies/publishedBotStrategy.d.ts +2 -1
  34. package/dist/src/subscribeEvent.d.ts +33 -0
  35. package/dist/src/subscribeEvent.js +7 -0
  36. package/dist/src/subscribeEvent.js.map +1 -0
  37. package/dist/src/userAgentHelper.d.ts +26 -0
  38. package/dist/src/userAgentHelper.js +52 -0
  39. package/dist/src/userAgentHelper.js.map +1 -0
  40. package/package.json +2 -2
  41. package/src/connectionSettings.ts +4 -1
  42. package/src/copilotStudioClient.ts +238 -32
  43. package/src/copilotStudioConnectionSettings.ts +7 -0
  44. package/src/copilotStudioWebChat.ts +75 -12
  45. package/src/executeTurnRequest.ts +7 -2
  46. package/src/index.ts +5 -0
  47. package/src/powerPlatformEnvironment.ts +26 -7
  48. package/src/responses.ts +75 -0
  49. package/src/scopeHelper.ts +22 -0
  50. package/src/startRequest.ts +48 -0
  51. package/src/strategies/prebuiltBotStrategy.ts +1 -1
  52. package/src/strategies/publishedBotStrategy.ts +1 -1
  53. package/src/subscribeEvent.ts +38 -0
  54. package/src/userAgentHelper.ts +49 -0
@@ -5,12 +5,15 @@
5
5
 
6
6
  import { createEventSource, EventSourceClient } from 'eventsource-client'
7
7
  import { ConnectionSettings } from './connectionSettings'
8
- import { getCopilotStudioConnectionUrl, getTokenAudience } from './powerPlatformEnvironment'
8
+ import { getCopilotStudioConnectionUrl, getCopilotStudioSubscribeUrl } from './powerPlatformEnvironment'
9
9
  import { Activity, ActivityTypes, ConversationAccount } from '@microsoft/agents-activity'
10
10
  import { ExecuteTurnRequest } from './executeTurnRequest'
11
11
  import { debug } from '@microsoft/agents-activity/logger'
12
- import { version } from '../package.json'
13
- import os from 'os'
12
+ import { UserAgentHelper } from './userAgentHelper'
13
+ import { ScopeHelper } from './scopeHelper'
14
+ import { StartRequest } from './startRequest'
15
+ import { StartResponse, ExecuteTurnResponse, createStartResponse, createExecuteTurnResponse } from './responses'
16
+ import { SubscribeEvent } from './subscribeEvent'
14
17
 
15
18
  const logger = debug('copilot-studio:client')
16
19
 
@@ -36,8 +39,9 @@ export class CopilotStudioClient {
36
39
  * This is used for authentication token audience configuration.
37
40
  * @param settings Copilot Studio connection settings.
38
41
  * @returns The scope URL for token audience.
42
+ * @deprecated Use ScopeHelper.getScopeFromSettings instead.
39
43
  */
40
- static scopeFromSettings: (settings: ConnectionSettings) => string = getTokenAudience
44
+ static scopeFromSettings: (settings: ConnectionSettings) => string = ScopeHelper.getScopeFromSettings
41
45
 
42
46
  /**
43
47
  * Creates an instance of CopilotStudioClient.
@@ -49,6 +53,17 @@ export class CopilotStudioClient {
49
53
  this.token = token
50
54
  }
51
55
 
56
+ /**
57
+ * Logs a diagnostic message if diagnostics are enabled.
58
+ * @param message The message to log.
59
+ * @param args Additional arguments to log.
60
+ */
61
+ private logDiagnostic (message: string, ...args: any[]): void {
62
+ if (this.settings.enableDiagnostics) {
63
+ logger.info(`[DIAGNOSTICS] ${message}`, ...args)
64
+ }
65
+ }
66
+
52
67
  /**
53
68
  * Streams activities from the Copilot Studio service using eventsource-client.
54
69
  * @param url The connection URL for Copilot Studio.
@@ -57,6 +72,10 @@ export class CopilotStudioClient {
57
72
  * @returns An async generator yielding the Agent's Activities.
58
73
  */
59
74
  private async * postRequestAsync (url: string, body?: any, method: string = 'POST'): AsyncGenerator<Activity> {
75
+ this.logDiagnostic(`Request URL: ${url}`)
76
+ this.logDiagnostic(`Request Method: ${method}`)
77
+ this.logDiagnostic('Request Body:', body ? JSON.stringify(body, null, 2) : 'none')
78
+
60
79
  logger.debug(`>>> SEND TO ${url}`)
61
80
 
62
81
  const streamMap = new Map<string, { text: string, sequence: number }[]>()
@@ -65,7 +84,7 @@ export class CopilotStudioClient {
65
84
  url,
66
85
  headers: {
67
86
  Authorization: `Bearer ${this.token}`,
68
- 'User-Agent': CopilotStudioClient.getProductInfo(),
87
+ 'User-Agent': UserAgentHelper.getProductInfo(),
69
88
  'Content-Type': 'application/json',
70
89
  Accept: 'text/event-stream'
71
90
  },
@@ -139,26 +158,6 @@ export class CopilotStudioClient {
139
158
  }
140
159
  }
141
160
 
142
- /**
143
- * Appends this package.json version to the User-Agent header.
144
- * - For browser environments, it includes the user agent of the browser.
145
- * - For Node.js environments, it includes the Node.js version, platform, architecture, and release.
146
- * @returns A string containing the product information, including version and user agent.
147
- */
148
- private static getProductInfo (): string {
149
- const versionString = `CopilotStudioClient.agents-sdk-js/${version}`
150
- let userAgent: string
151
-
152
- if (typeof window !== 'undefined' && window.navigator) {
153
- userAgent = `${versionString} ${navigator.userAgent}`
154
- } else {
155
- userAgent = `${versionString} nodejs/${process.version} ${os.platform()}-${os.arch()}/${os.release()}`
156
- }
157
-
158
- logger.debug(`User-Agent: ${userAgent}`)
159
- return userAgent
160
- }
161
-
162
161
  private processResponseHeaders (responseHeaders: Headers): void {
163
162
  if (this.settings.useExperimentalEndpoint && !this.settings.directConnectUrl?.trim()) {
164
163
  const islandExperimentalUrl = responseHeaders?.get(CopilotStudioClient.islandExperimentalUrlHeaderKey)
@@ -179,19 +178,54 @@ export class CopilotStudioClient {
179
178
  sanitizedHeaders.set(key, value)
180
179
  }
181
180
  })
182
- logger.debug('Headers received:', sanitizedHeaders)
181
+ this.logDiagnostic('Response Headers:', sanitizedHeaders)
183
182
  }
184
183
 
184
+ /**
185
+ * Starts a new conversation with the Copilot Studio service using a StartRequest.
186
+ * @param request The request parameters for starting the conversation.
187
+ * @returns An async generator yielding the Agent's Activities.
188
+ */
189
+ public startConversationStreaming (request: StartRequest): AsyncGenerator<Activity>
190
+
185
191
  /**
186
192
  * Starts a new conversation with the Copilot Studio service.
187
193
  * @param emitStartConversationEvent Whether to emit a start conversation event. Defaults to true.
188
194
  * @returns An async generator yielding the Agent's Activities.
189
195
  */
190
- public async * startConversationStreaming (emitStartConversationEvent: boolean = true): AsyncGenerator<Activity> {
191
- const uriStart: string = getCopilotStudioConnectionUrl(this.settings)
192
- const body = { emitStartConversationEvent }
196
+ public startConversationStreaming (emitStartConversationEvent?: boolean): AsyncGenerator<Activity>
197
+
198
+ /**
199
+ * Implementation of startConversationStreaming with overloads.
200
+ */
201
+ public async * startConversationStreaming (
202
+ requestOrFlag?: StartRequest | boolean
203
+ ): AsyncGenerator<Activity> {
204
+ // Normalize input to StartRequest
205
+ let request: StartRequest
206
+
207
+ if (typeof requestOrFlag === 'boolean' || requestOrFlag === undefined) {
208
+ // Legacy call: startConversationStreaming(true/false)
209
+ request = {
210
+ emitStartConversationEvent: requestOrFlag ?? true
211
+ }
212
+ } else {
213
+ // New call: startConversationStreaming({ locale: 'en-US', ... })
214
+ request = requestOrFlag
215
+ }
216
+
217
+ const uriStart: string = getCopilotStudioConnectionUrl(this.settings, request.conversationId)
218
+ const body: any = {
219
+ emitStartConversationEvent: request.emitStartConversationEvent ?? true
220
+ }
221
+
222
+ // Add locale to body if provided
223
+ if (request.locale) {
224
+ body.locale = request.locale
225
+ }
193
226
 
194
- logger.info('Starting conversation ...')
227
+ logger.info('Starting conversation ...', request)
228
+ this.logDiagnostic('Start conversation request:', body)
195
229
 
196
230
  yield * this.postRequestAsync(uriStart, body, 'POST')
197
231
  }
@@ -211,15 +245,78 @@ export class CopilotStudioClient {
211
245
  yield * this.postRequestAsync(uriExecute, qbody, 'POST')
212
246
  }
213
247
 
248
+ /**
249
+ * Executes a turn in an existing conversation by sending an activity.
250
+ * This method provides explicit control over the conversation ID.
251
+ * @param activity The activity to send.
252
+ * @param conversationId The ID of the conversation. Required.
253
+ * @returns An async generator yielding the Agent's Activities.
254
+ * @throws Error if conversationId is not provided.
255
+ */
256
+ public async * executeStreaming (
257
+ activity: Activity,
258
+ conversationId: string
259
+ ): AsyncGenerator<Activity> {
260
+ if (!conversationId || !conversationId.trim()) {
261
+ throw new Error('conversationId is required for executeStreaming')
262
+ }
263
+
264
+ const uriExecute = getCopilotStudioConnectionUrl(this.settings, conversationId)
265
+ const request: ExecuteTurnRequest = new ExecuteTurnRequest(activity, conversationId)
266
+
267
+ logger.info('Executing turn with conversation ID:', conversationId)
268
+ this.logDiagnostic('Execute turn request:', {
269
+ conversationId,
270
+ activityType: activity.type,
271
+ activityText: activity.text
272
+ })
273
+
274
+ yield * this.postRequestAsync(uriExecute, request, 'POST')
275
+ }
276
+
277
+ /**
278
+ * Executes a turn in an existing conversation by sending an activity.
279
+ * @param activity The activity to send.
280
+ * @param conversationId The ID of the conversation. Required.
281
+ * @returns A promise yielding an array of activities.
282
+ * @throws Error if conversationId is not provided.
283
+ * @deprecated Use executeStreaming instead.
284
+ */
285
+ public async execute (
286
+ activity: Activity,
287
+ conversationId: string
288
+ ): Promise<Activity[]> {
289
+ const result: Activity[] = []
290
+ for await (const value of this.executeStreaming(activity, conversationId)) {
291
+ result.push(value)
292
+ }
293
+ return result
294
+ }
295
+
296
+ /**
297
+ * Starts a new conversation with the Copilot Studio service using a StartRequest.
298
+ * @param request The request parameters for starting the conversation.
299
+ * @returns A promise yielding an array of activities.
300
+ * @deprecated Use startConversationStreaming instead.
301
+ */
302
+ public async startConversationAsync (request: StartRequest): Promise<Activity[]>
303
+
214
304
  /**
215
305
  * Starts a new conversation with the Copilot Studio service.
216
306
  * @param emitStartConversationEvent Whether to emit a start conversation event. Defaults to true.
217
307
  * @returns A promise yielding an array of activities.
218
308
  * @deprecated Use startConversationStreaming instead.
219
309
  */
220
- public async startConversationAsync (emitStartConversationEvent: boolean = true): Promise<Activity[]> {
310
+ public async startConversationAsync (emitStartConversationEvent?: boolean): Promise<Activity[]>
311
+
312
+ /**
313
+ * Implementation of startConversationAsync with overloads.
314
+ */
315
+ public async startConversationAsync (
316
+ requestOrFlag?: StartRequest | boolean
317
+ ): Promise<Activity[]> {
221
318
  const result: Activity[] = []
222
- for await (const value of this.startConversationStreaming(emitStartConversationEvent)) {
319
+ for await (const value of this.startConversationStreaming(requestOrFlag as any)) {
223
320
  result.push(value)
224
321
  }
225
322
  return result
@@ -265,4 +362,113 @@ export class CopilotStudioClient {
265
362
  }
266
363
  return result
267
364
  }
365
+
366
+ /**
367
+ * Starts a new conversation and returns a typed response.
368
+ * @param request The request parameters for starting the conversation.
369
+ * @returns A promise yielding a StartResponse with activities and conversation metadata.
370
+ */
371
+ public async startConversationWithResponse (request?: StartRequest | boolean): Promise<StartResponse> {
372
+ const activities: Activity[] = []
373
+ let finalConversationId = ''
374
+
375
+ for await (const activity of this.startConversationStreaming(request as any)) {
376
+ activities.push(activity)
377
+ if (activity.conversation?.id) {
378
+ finalConversationId = activity.conversation.id
379
+ }
380
+ }
381
+
382
+ // Fall back to instance conversationId if not found in activities
383
+ finalConversationId = finalConversationId || this.conversationId
384
+
385
+ return createStartResponse(activities, finalConversationId)
386
+ }
387
+
388
+ /**
389
+ * Executes a turn and returns a typed response.
390
+ * @param activity The activity to send.
391
+ * @param conversationId The conversation ID.
392
+ * @returns A promise yielding an ExecuteTurnResponse with activities and metadata.
393
+ */
394
+ public async executeWithResponse (
395
+ activity: Activity,
396
+ conversationId: string
397
+ ): Promise<ExecuteTurnResponse> {
398
+ const activities: Activity[] = []
399
+
400
+ for await (const value of this.executeStreaming(activity, conversationId)) {
401
+ activities.push(value)
402
+ }
403
+
404
+ return createExecuteTurnResponse(activities, conversationId)
405
+ }
406
+
407
+ /**
408
+ * Subscribes to a conversation to receive events via Server-Sent Events (SSE).
409
+ * This method allows resumption from a specific event ID.
410
+ * @param conversationId The ID of the conversation to subscribe to.
411
+ * @param lastReceivedEventId Optional. The last received event ID for resumption.
412
+ * @returns An async generator yielding SubscribeEvent objects containing activities and event IDs.
413
+ */
414
+ public async * subscribeAsync (
415
+ conversationId: string,
416
+ lastReceivedEventId?: string
417
+ ): AsyncGenerator<SubscribeEvent> {
418
+ if (!conversationId || !conversationId.trim()) {
419
+ throw new Error('conversationId is required for subscribeAsync')
420
+ }
421
+
422
+ const url = getCopilotStudioSubscribeUrl(this.settings, conversationId)
423
+
424
+ logger.info('Subscribing to conversation:', conversationId)
425
+ this.logDiagnostic('Subscribe request:', { conversationId, lastReceivedEventId, url })
426
+
427
+ const eventSource: EventSourceClient = createEventSource({
428
+ url,
429
+ headers: {
430
+ Authorization: `Bearer ${this.token}`,
431
+ 'User-Agent': UserAgentHelper.getProductInfo(),
432
+ Accept: 'text/event-stream',
433
+ ...(lastReceivedEventId && { 'Last-Event-ID': lastReceivedEventId })
434
+ },
435
+ method: 'GET',
436
+ fetch: async (url, init) => {
437
+ const response = await fetch(url, init)
438
+ this.processResponseHeaders(response.headers)
439
+ return response
440
+ }
441
+ })
442
+
443
+ try {
444
+ for await (const { data, event, id } of eventSource) {
445
+ if (data && event === 'activity') {
446
+ try {
447
+ const activity = Activity.fromJson(data)
448
+ const subscribeEvent: SubscribeEvent = {
449
+ activity,
450
+ eventId: id
451
+ }
452
+
453
+ logger.debug(`Received activity via subscription, event ID: ${id}`)
454
+ this.logDiagnostic('Subscribe event received:', { eventId: id, activityType: activity.type })
455
+
456
+ yield subscribeEvent
457
+ } catch (error) {
458
+ logger.error('Failed to parse activity in subscription:', error)
459
+ }
460
+ } else if (event === 'end') {
461
+ logger.debug('Subscription stream complete')
462
+ break
463
+ }
464
+
465
+ if (eventSource.readyState === 'closed') {
466
+ logger.debug('Subscription connection closed')
467
+ break
468
+ }
469
+ }
470
+ } finally {
471
+ eventSource.close()
472
+ }
473
+ }
268
474
  }
@@ -36,6 +36,13 @@ export interface CopilotStudioConnectionSettings {
36
36
  /** Directs Copilot Studio Client to use the experimental endpoint if available. Default value is false. */
37
37
  useExperimentalEndpoint?: boolean
38
38
 
39
+ /**
40
+ * Enables diagnostic logging for debugging purposes.
41
+ * When enabled, detailed logs about requests, responses, and connection status will be emitted.
42
+ * Default value is false.
43
+ */
44
+ enableDiagnostics?: boolean
45
+
39
46
  /**
40
47
  * The login authority to use for the connection.
41
48
  * @deprecated This property will not be supported in future versions. Handle the auth properties in the agent.
@@ -26,6 +26,32 @@ export interface CopilotStudioWebChatSettings {
26
26
  * @default false
27
27
  */
28
28
  showTyping?: boolean;
29
+
30
+ /**
31
+ * An existing conversation ID to resume. When provided, the connection will
32
+ * send subsequent messages to this conversation instead of starting a new one.
33
+ *
34
+ * By default, providing a conversationId will skip the initial
35
+ * `startConversationStreaming()` call. Override this with the
36
+ * `startConversation` setting.
37
+ *
38
+ * **Note:** The server does not validate conversation IDs. A non-existent
39
+ * GUID will silently create a new conversation under that ID, while a
40
+ * non-GUID string may cause the server to return no response. Only pass
41
+ * IDs that were previously captured from a real conversation.
42
+ */
43
+ conversationId?: string;
44
+
45
+ /**
46
+ * Controls whether `startConversationStreaming()` is called when the
47
+ * connection is first subscribed to.
48
+ *
49
+ * - `undefined` (default): starts a new conversation only when no
50
+ * `conversationId` is provided (`!conversationId`).
51
+ * - `true`: always starts a conversation, even when resuming.
52
+ * - `false`: never starts a conversation, even for new connections.
53
+ */
54
+ startConversation?: boolean;
29
55
  }
30
56
 
31
57
  /**
@@ -61,6 +87,13 @@ export interface CopilotStudioWebChatConnection {
61
87
  */
62
88
  activity$: Observable<Partial<Activity>>;
63
89
 
90
+ /**
91
+ * The active conversation ID. Set from `CopilotStudioWebChatSettings.conversationId`
92
+ * when resuming, or captured from the first response activity for new conversations.
93
+ * Returns `undefined` until a conversation has been established.
94
+ */
95
+ readonly conversationId: string | undefined;
96
+
64
97
  /**
65
98
  * Posts a user activity to the Copilot Studio service and returns an observable
66
99
  * that emits the activity ID once the message is successfully sent.
@@ -224,9 +257,19 @@ export class CopilotStudioWebChat {
224
257
  settings?: CopilotStudioWebChatSettings
225
258
  ): CopilotStudioWebChatConnection {
226
259
  logger.info('--> Creating connection between Copilot Studio and WebChat ...')
260
+
261
+ const normalizedConversationId =
262
+ settings?.conversationId && settings.conversationId.trim() !== ''
263
+ ? settings.conversationId.trim()
264
+ : undefined
265
+ const shouldStart = settings?.startConversation ?? !normalizedConversationId
266
+
227
267
  let sequence = 0
228
268
  let activitySubscriber: Subscriber<Partial<Activity>> | undefined
229
269
  let conversation: ConversationAccount | undefined
270
+ let activeConversationId: string | undefined = normalizedConversationId
271
+ let ended = false
272
+ let started = false
230
273
 
231
274
  const connectionStatus$ = new BehaviorSubject(0)
232
275
  const activity$ = createObservable<Partial<Activity>>(async (subscriber) => {
@@ -237,22 +280,29 @@ export class CopilotStudioWebChat {
237
280
  await Promise.resolve() // Webchat requires an extra tick to process the connection status change
238
281
  })
239
282
 
283
+ // When resuming (shouldStart === false), transition straight to connected
284
+ if (!shouldStart || started) {
285
+ await handleAcknowledgementOnce()
286
+ return
287
+ }
288
+ started = true
289
+
240
290
  logger.debug('--> Connection established.')
241
291
  notifyTyping()
242
292
 
243
- if (connectionStatus$.value < 2) {
244
- for await (const activity of client.startConversationStreaming()) {
245
- delete activity.replyToId
246
- if (!conversation && activity.conversation) {
247
- conversation = activity.conversation
248
- await handleAcknowledgementOnce()
249
- }
250
-
251
- notifyActivity(activity)
293
+ for await (const activity of client.startConversationStreaming()) {
294
+ delete activity.replyToId
295
+ if (!conversation && activity.conversation) {
296
+ conversation = activity.conversation
297
+ }
298
+ if (activity.conversation?.id) {
299
+ activeConversationId = activity.conversation.id
252
300
  }
253
- // If no activities received from bot, we should still acknowledge.
254
301
  await handleAcknowledgementOnce()
302
+ notifyActivity(activity)
255
303
  }
304
+ // If no activities received from bot, we should still acknowledge.
305
+ await handleAcknowledgementOnce()
256
306
  })
257
307
 
258
308
  const notifyActivity = (activity: Partial<Activity>) => {
@@ -283,6 +333,11 @@ export class CopilotStudioWebChat {
283
333
  return {
284
334
  connectionStatus$,
285
335
  activity$,
336
+
337
+ get conversationId () {
338
+ return activeConversationId
339
+ },
340
+
286
341
  postActivity (activity: Activity) {
287
342
  logger.info('--> Preparing to send activity to Copilot Studio ...')
288
343
 
@@ -290,6 +345,10 @@ export class CopilotStudioWebChat {
290
345
  throw new Error('Activity cannot be null.')
291
346
  }
292
347
 
348
+ if (ended) {
349
+ throw new Error('Connection has been ended.')
350
+ }
351
+
293
352
  if (!activitySubscriber) {
294
353
  throw new Error('Activity subscriber is not initialized.')
295
354
  }
@@ -309,8 +368,11 @@ export class CopilotStudioWebChat {
309
368
  // Notify WebChat immediately that the message was sent
310
369
  subscriber.next(newActivity.id!)
311
370
 
312
- // Stream the agent's response, but don't block the UI
313
- for await (const responseActivity of client.sendActivityStreaming(newActivity)) {
371
+ // Stream the agent's response, passing activeConversationId for URL routing
372
+ for await (const responseActivity of client.sendActivityStreaming(newActivity, activeConversationId)) {
373
+ if (!activeConversationId && responseActivity.conversation?.id) {
374
+ activeConversationId = responseActivity.conversation.id
375
+ }
314
376
  notifyActivity(responseActivity)
315
377
  logger.info('<-- Activity received correctly from Copilot Studio.')
316
378
  }
@@ -325,6 +387,7 @@ export class CopilotStudioWebChat {
325
387
 
326
388
  end () {
327
389
  logger.info('--> Ending connection between Copilot Studio and WebChat ...')
390
+ ended = true
328
391
  connectionStatus$.complete()
329
392
  if (activitySubscriber) {
330
393
  activitySubscriber.complete()
@@ -7,17 +7,22 @@ import { Activity } from '@microsoft/agents-activity'
7
7
 
8
8
  /**
9
9
  * Represents a request to execute a turn in a conversation.
10
- * This class encapsulates the activity to be executed during the turn.
10
+ * This class encapsulates the activity and optional conversation context.
11
11
  */
12
12
  export class ExecuteTurnRequest {
13
13
  /** The activity to be executed. */
14
14
  activity?: Activity
15
15
 
16
+ /** Optional conversation ID for this turn. */
17
+ conversationId?: string
18
+
16
19
  /**
17
20
  * Creates an instance of ExecuteTurnRequest.
18
21
  * @param activity The activity to be executed.
22
+ * @param conversationId Optional conversation ID.
19
23
  */
20
- constructor (activity?: Activity) {
24
+ constructor (activity?: Activity, conversationId?: string) {
21
25
  this.activity = activity
26
+ this.conversationId = conversationId
22
27
  }
23
28
  }
package/src/index.ts CHANGED
@@ -6,3 +6,8 @@ export * from './copilotStudioWebChat.js'
6
6
  export * from './executeTurnRequest.js'
7
7
  export * from './powerPlatformCloud.js'
8
8
  export * from './powerPlatformEnvironment.js'
9
+ export * from './responses.js'
10
+ export * from './scopeHelper.js'
11
+ export * from './startRequest.js'
12
+ export * from './subscribeEvent.js'
13
+ export * from './userAgentHelper.js'
@@ -34,13 +34,6 @@ export function getCopilotStudioConnectionUrl (
34
34
  throw new Error('directConnectUrl must be a valid URL')
35
35
  }
36
36
 
37
- // FIX for Missing Tenant ID
38
- if (settings.directConnectUrl.toLowerCase().includes('tenants/00000000-0000-0000-0000-000000000000')) {
39
- logger.warn(`Direct connection cannot be used, forcing default settings flow. Tenant ID is missing in the URL: ${settings.directConnectUrl}`)
40
- // Direct connection cannot be used, ejecting and forcing the normal settings flow:
41
- return getCopilotStudioConnectionUrl({ ...settings, directConnectUrl: '' }, conversationId)
42
- }
43
-
44
37
  return createURL(settings.directConnectUrl, conversationId).href
45
38
  }
46
39
 
@@ -80,6 +73,32 @@ export function getCopilotStudioConnectionUrl (
80
73
  return url
81
74
  }
82
75
 
76
+ /**
77
+ * Generates the subscribe URL for Copilot Studio Server-Sent Events (SSE).
78
+ * @param settings - The connection settings.
79
+ * @param conversationId - The conversation ID to subscribe to.
80
+ * @returns The subscribe URL.
81
+ * @throws Will throw an error if required settings are missing or invalid.
82
+ */
83
+ export function getCopilotStudioSubscribeUrl (
84
+ settings: ConnectionSettings,
85
+ conversationId: string
86
+ ): string {
87
+ if (!conversationId || !conversationId.trim()) {
88
+ throw new Error('conversationId is required for subscribe URL')
89
+ }
90
+
91
+ const baseUrl = getCopilotStudioConnectionUrl(settings, conversationId)
92
+ const url = new URL(baseUrl)
93
+ url.pathname = url.pathname.endsWith('/')
94
+ ? `${url.pathname}subscribe`
95
+ : `${url.pathname}/subscribe`
96
+ const subscribeUrl = url.href
97
+
98
+ logger.debug(`Generated Copilot Studio subscribe URL: ${subscribeUrl}`)
99
+ return subscribeUrl
100
+ }
101
+
83
102
  /**
84
103
  * Returns the Power Platform API Audience.
85
104
  * @param settings - Configuration Settings to use.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { Activity } from '@microsoft/agents-activity'
7
+
8
+ /**
9
+ * Base interface for all Copilot Studio API responses.
10
+ */
11
+ export interface ResponseBase {
12
+ /**
13
+ * The activities returned by the service.
14
+ */
15
+ activities: Activity[]
16
+
17
+ /**
18
+ * The conversation ID associated with this response.
19
+ */
20
+ conversationId: string
21
+ }
22
+
23
+ /**
24
+ * Response returned when starting a new conversation.
25
+ */
26
+ export interface StartResponse extends ResponseBase {
27
+ /**
28
+ * Indicates whether this is a new conversation.
29
+ */
30
+ isNewConversation: boolean
31
+ }
32
+
33
+ /**
34
+ * Response returned when executing a turn in an existing conversation.
35
+ */
36
+ export interface ExecuteTurnResponse extends ResponseBase {
37
+ /**
38
+ * The number of activities in this response.
39
+ */
40
+ activityCount: number
41
+ }
42
+
43
+ /**
44
+ * Creates a StartResponse from an array of activities.
45
+ * @param activities The activities to include in the response.
46
+ * @param conversationId The conversation ID.
47
+ * @returns A new StartResponse object.
48
+ */
49
+ export function createStartResponse (
50
+ activities: Activity[],
51
+ conversationId: string
52
+ ): StartResponse {
53
+ return {
54
+ activities,
55
+ conversationId,
56
+ isNewConversation: true
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Creates an ExecuteTurnResponse from an array of activities.
62
+ * @param activities The activities to include in the response.
63
+ * @param conversationId The conversation ID.
64
+ * @returns A new ExecuteTurnResponse object.
65
+ */
66
+ export function createExecuteTurnResponse (
67
+ activities: Activity[],
68
+ conversationId: string
69
+ ): ExecuteTurnResponse {
70
+ return {
71
+ activities,
72
+ conversationId,
73
+ activityCount: activities.length
74
+ }
75
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ConnectionSettings } from './connectionSettings'
7
+ import { getTokenAudience } from './powerPlatformEnvironment'
8
+
9
+ /**
10
+ * Utility class for generating authentication scope URLs for Copilot Studio.
11
+ */
12
+ export class ScopeHelper {
13
+ /**
14
+ * Returns the scope URL needed to connect to Copilot Studio from the connection settings.
15
+ * This is used for authentication token audience configuration.
16
+ * @param settings Copilot Studio connection settings.
17
+ * @returns The scope URL for token audience (e.g., "https://api.powerplatform.com/.default").
18
+ */
19
+ static getScopeFromSettings (settings: ConnectionSettings): string {
20
+ return getTokenAudience(settings)
21
+ }
22
+ }