@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.
- package/dist/package.json +2 -2
- package/dist/src/browser.mjs +5 -5
- package/dist/src/browser.mjs.map +4 -4
- package/dist/src/connectionSettings.d.ts +2 -0
- package/dist/src/connectionSettings.js +5 -2
- package/dist/src/connectionSettings.js.map +1 -1
- package/dist/src/copilotStudioClient.d.ts +61 -6
- package/dist/src/copilotStudioClient.js +176 -38
- package/dist/src/copilotStudioClient.js.map +1 -1
- package/dist/src/copilotStudioConnectionSettings.d.ts +6 -0
- package/dist/src/copilotStudioWebChat.d.ts +30 -0
- package/dist/src/copilotStudioWebChat.js +38 -11
- package/dist/src/copilotStudioWebChat.js.map +1 -1
- package/dist/src/executeTurnRequest.d.ts +5 -2
- package/dist/src/executeTurnRequest.js +4 -2
- package/dist/src/executeTurnRequest.js.map +1 -1
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/powerPlatformEnvironment.d.ts +8 -0
- package/dist/src/powerPlatformEnvironment.js +21 -6
- package/dist/src/powerPlatformEnvironment.js.map +1 -1
- package/dist/src/responses.d.ts +50 -0
- package/dist/src/responses.js +35 -0
- package/dist/src/responses.js.map +1 -0
- package/dist/src/scopeHelper.d.ts +17 -0
- package/dist/src/scopeHelper.js +24 -0
- package/dist/src/scopeHelper.js.map +1 -0
- package/dist/src/startRequest.d.ts +34 -0
- package/dist/src/startRequest.js +22 -0
- package/dist/src/startRequest.js.map +1 -0
- package/dist/src/strategies/prebuiltBotStrategy.d.ts +2 -1
- package/dist/src/strategies/publishedBotStrategy.d.ts +2 -1
- package/dist/src/subscribeEvent.d.ts +33 -0
- package/dist/src/subscribeEvent.js +7 -0
- package/dist/src/subscribeEvent.js.map +1 -0
- package/dist/src/userAgentHelper.d.ts +26 -0
- package/dist/src/userAgentHelper.js +52 -0
- package/dist/src/userAgentHelper.js.map +1 -0
- package/package.json +2 -2
- package/src/connectionSettings.ts +4 -1
- package/src/copilotStudioClient.ts +238 -32
- package/src/copilotStudioConnectionSettings.ts +7 -0
- package/src/copilotStudioWebChat.ts +75 -12
- package/src/executeTurnRequest.ts +7 -2
- package/src/index.ts +5 -0
- package/src/powerPlatformEnvironment.ts +26 -7
- package/src/responses.ts +75 -0
- package/src/scopeHelper.ts +22 -0
- package/src/startRequest.ts +48 -0
- package/src/strategies/prebuiltBotStrategy.ts +1 -1
- package/src/strategies/publishedBotStrategy.ts +1 -1
- package/src/subscribeEvent.ts +38 -0
- 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,
|
|
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 {
|
|
13
|
-
import
|
|
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 =
|
|
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':
|
|
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
|
-
|
|
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
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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,
|
|
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
|
|
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.
|
package/src/responses.ts
ADDED
|
@@ -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
|
+
}
|