@microsoft/agents-hosting 1.7.2 → 1.8.0-beta.36.gb033f0abc1
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/README.md +64 -2
- package/dist/package.json +3 -3
- package/dist/src/agent-client/agentClient.d.ts +18 -6
- package/dist/src/agent-client/agentClient.js +34 -18
- package/dist/src/agent-client/agentClient.js.map +1 -1
- package/dist/src/agent-client/agentResponseHandler.d.ts +14 -9
- package/dist/src/agent-client/agentResponseHandler.js +10 -5
- package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
- package/dist/src/agent-client/createAgentResponseHandler.d.ts +7 -2
- package/dist/src/agent-client/createAgentResponseHandler.js +55 -8
- package/dist/src/agent-client/createAgentResponseHandler.js.map +1 -1
- package/dist/src/app/adaptiveCards/activityValueParsers.d.ts +1 -4
- package/dist/src/app/adaptiveCards/activityValueParsers.js +2 -5
- package/dist/src/app/adaptiveCards/activityValueParsers.js.map +1 -1
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.d.ts +18 -0
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.js +53 -23
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.js.map +1 -1
- package/dist/src/app/agentApplication.d.ts +3 -0
- package/dist/src/app/agentApplication.js +140 -81
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/attachmentDownloader.d.ts +4 -1
- package/dist/src/app/attachmentDownloader.js +27 -5
- package/dist/src/app/attachmentDownloader.js.map +1 -1
- package/dist/src/app/auth/handlers/agenticAuthorization.d.ts +1 -1
- package/dist/src/app/auth/handlers/azureBotAuthorization.d.ts +1 -1
- package/dist/src/app/auth/types.d.ts +3 -4
- package/dist/src/app/index.d.ts +2 -0
- package/dist/src/app/index.js.map +1 -1
- package/dist/src/app/streaming/streamingResponse.d.ts +56 -0
- package/dist/src/app/streaming/streamingResponse.js +250 -26
- package/dist/src/app/streaming/streamingResponse.js.map +1 -1
- package/dist/src/app/teamsAttachmentDownloader.d.ts +3 -1
- package/dist/src/app/teamsAttachmentDownloader.js +12 -1
- package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
- package/dist/src/auth/authConfiguration.js +12 -0
- package/dist/src/auth/authConfiguration.js.map +1 -1
- package/dist/src/auth/index.d.ts +2 -1
- package/dist/src/auth/index.js +2 -1
- package/dist/src/auth/index.js.map +1 -1
- package/dist/src/auth/jwt-middleware.d.ts +6 -0
- package/dist/src/auth/jwt-middleware.js +263 -13
- package/dist/src/auth/jwt-middleware.js.map +1 -1
- package/dist/src/auth/msal/msalConnectionSettings.d.ts +8 -0
- package/dist/src/auth/msal/msalHttpRetryHandlerHelper.d.ts +34 -0
- package/dist/src/auth/msal/msalHttpRetryHandlerHelper.js +126 -0
- package/dist/src/auth/msal/msalHttpRetryHandlerHelper.js.map +1 -0
- package/dist/src/auth/msal/msalTokenProvider.d.ts +1 -0
- package/dist/src/auth/msal/msalTokenProvider.js +23 -14
- package/dist/src/auth/msal/msalTokenProvider.js.map +1 -1
- package/dist/src/auth/settings.d.ts +18 -0
- package/dist/src/auth/settings.js +8 -2
- package/dist/src/auth/settings.js.map +1 -1
- package/dist/src/cloudAdapter.d.ts +29 -2
- package/dist/src/cloudAdapter.js +44 -2
- package/dist/src/cloudAdapter.js.map +1 -1
- package/dist/src/errorHelper.js +137 -101
- package/dist/src/errorHelper.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/invoke/adaptiveCardInvokeValue.d.ts +5 -0
- package/dist/src/oauth/userTokenClient.d.ts +0 -1
- package/dist/src/oauth/userTokenClient.js +0 -1
- package/dist/src/oauth/userTokenClient.js.map +1 -1
- package/dist/src/observability/traces.d.ts +6 -0
- package/dist/src/observability/traces.js +20 -2
- package/dist/src/observability/traces.js.map +1 -1
- package/dist/src/outboundHostValidator.d.ts +47 -0
- package/dist/src/outboundHostValidator.js +149 -0
- package/dist/src/outboundHostValidator.js.map +1 -0
- package/dist/src/state/agentState.d.ts +5 -0
- package/dist/src/state/agentState.js +16 -3
- package/dist/src/state/agentState.js.map +1 -1
- package/package.json +3 -3
- package/src/agent-client/agentClient.ts +46 -23
- package/src/agent-client/agentResponseHandler.ts +14 -9
- package/src/agent-client/createAgentResponseHandler.ts +70 -9
- package/src/app/adaptiveCards/activityValueParsers.ts +3 -9
- package/src/app/adaptiveCards/adaptiveCardsActions.ts +52 -24
- package/src/app/agentApplication.ts +149 -77
- package/src/app/attachmentDownloader.ts +25 -5
- package/src/app/auth/handlers/agenticAuthorization.ts +1 -1
- package/src/app/auth/handlers/azureBotAuthorization.ts +1 -1
- package/src/app/auth/types.ts +3 -3
- package/src/app/index.ts +8 -0
- package/src/app/streaming/streamingResponse.ts +276 -22
- package/src/app/teamsAttachmentDownloader.ts +11 -1
- package/src/auth/authConfiguration.ts +12 -0
- package/src/auth/index.ts +2 -1
- package/src/auth/jwt-middleware.ts +298 -14
- package/src/auth/msal/msalConnectionSettings.ts +10 -0
- package/src/auth/msal/msalHttpRetryHandlerHelper.ts +161 -0
- package/src/auth/msal/msalTokenProvider.ts +32 -14
- package/src/auth/settings.ts +18 -3
- package/src/cloudAdapter.ts +56 -3
- package/src/errorHelper.ts +155 -114
- package/src/index.ts +1 -0
- package/src/invoke/adaptiveCardInvokeValue.ts +5 -0
- package/src/oauth/userTokenClient.ts +0 -1
- package/src/observability/traces.ts +19 -1
- package/src/outboundHostValidator.ts +172 -0
- package/src/state/agentState.ts +24 -6
|
@@ -14,11 +14,18 @@ import { normalizeIncomingActivity } from '../activityWireCompat'
|
|
|
14
14
|
import { Errors } from '../errorHelper'
|
|
15
15
|
import { debug } from '@microsoft/agents-telemetry'
|
|
16
16
|
import { ConversationState } from '../state'
|
|
17
|
+
import { getAuthorizedAudience } from '../auth/jwt-middleware'
|
|
17
18
|
|
|
18
19
|
const logger = debug('agents:agent-client')
|
|
19
20
|
|
|
20
21
|
interface ConversationReferenceState {
|
|
21
22
|
conversationReference: ConversationReference
|
|
23
|
+
expectedAgentClientId?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface DelegatedConversationState {
|
|
27
|
+
conversationReference: ConversationReference
|
|
28
|
+
expectedAgentClientId: string
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
/**
|
|
@@ -31,7 +38,7 @@ export interface AgentResponseHandlerParams {
|
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
/**
|
|
34
|
-
* Framework-agnostic handler signature for the
|
|
41
|
+
* Framework-agnostic handler signature for the SDK-specific Activity callback endpoint.
|
|
35
42
|
*
|
|
36
43
|
* @remarks
|
|
37
44
|
* The handler is intended to be invoked by a thin framework-specific wrapper (such
|
|
@@ -45,7 +52,7 @@ export type AgentResponseHandler = (
|
|
|
45
52
|
) => Promise<void>
|
|
46
53
|
|
|
47
54
|
/**
|
|
48
|
-
* Creates a framework-agnostic handler for the
|
|
55
|
+
* Creates a framework-agnostic handler for the authenticated Activity callback endpoint.
|
|
49
56
|
*
|
|
50
57
|
* This is the core, Express-free implementation used by:
|
|
51
58
|
* - `configureResponseController` in `@microsoft/agents-hosting-express`
|
|
@@ -54,6 +61,11 @@ export type AgentResponseHandler = (
|
|
|
54
61
|
* Both wrappers register the canonical route
|
|
55
62
|
* `POST /api/agentresponse/v3/conversations/:conversationId/activities/:activityId`
|
|
56
63
|
* and forward the parsed body + path parameters to the handler returned here.
|
|
64
|
+
* The handler returns `401` for failed JWT authentication and `403` when the
|
|
65
|
+
* authenticated caller does not match the delegated agent stored for the
|
|
66
|
+
* conversation, or when that delegated state is missing or malformed.
|
|
67
|
+
*
|
|
68
|
+
* This callback contract is SDK-specific and is not the open A2A protocol.
|
|
57
69
|
*
|
|
58
70
|
* @param adapter - The CloudAdapter used for processing activities and managing conversations.
|
|
59
71
|
* @param agent - The ActivityHandler containing the agent logic.
|
|
@@ -65,21 +77,57 @@ export const createAgentResponseHandler = (
|
|
|
65
77
|
agent: ActivityHandler,
|
|
66
78
|
conversationState: ConversationState
|
|
67
79
|
): AgentResponseHandler => {
|
|
80
|
+
const appId = adapter.getClientId() ?? ''
|
|
81
|
+
const anonymousDevelopment = appId.length === 0 && process.env.NODE_ENV !== 'production'
|
|
82
|
+
if (anonymousDevelopment) {
|
|
83
|
+
emitAgentResponseWarning(
|
|
84
|
+
'The agent-response endpoint is using anonymous authentication outside production. Callback ownership cannot be cryptographically verified; configure a client ID before deployment.'
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
68
88
|
return async (req: Request, res: WebResponse, params: AgentResponseHandlerParams) => {
|
|
89
|
+
let middlewareError: any
|
|
90
|
+
let nextCalled = false
|
|
91
|
+
await adapter.authorizeRequest(req, res, (err?: any) => {
|
|
92
|
+
nextCalled = true
|
|
93
|
+
middlewareError = err
|
|
94
|
+
})
|
|
95
|
+
if (middlewareError) {
|
|
96
|
+
throw middlewareError
|
|
97
|
+
}
|
|
98
|
+
if (!nextCalled || res.headersSent) {
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
69
102
|
if (!req.body) {
|
|
70
103
|
throw ExceptionHelper.generateException(TypeError, Errors.MissingRequestBody)
|
|
71
104
|
}
|
|
72
105
|
const incoming = normalizeIncomingActivity(req.body)
|
|
73
106
|
const activity = Activity.fromObject(incoming)
|
|
74
107
|
|
|
75
|
-
logger.debug('received response
|
|
108
|
+
logger.debug('received delegated agent response')
|
|
76
109
|
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
const myTurnContext = new TurnContext(adapter, activity, CloudAdapter.createIdentity(appId))
|
|
110
|
+
const continuationAudience = getAuthorizedAudience(req) ?? appId
|
|
111
|
+
const myTurnContext = new TurnContext(adapter, activity, CloudAdapter.createIdentity(continuationAudience))
|
|
81
112
|
const conversationDataAccessor = conversationState.createProperty<ConversationReferenceState>(params.conversationId)
|
|
82
|
-
const
|
|
113
|
+
const incomingChannelId = activity.channelId!
|
|
114
|
+
const conversationRefState = await conversationDataAccessor.get(myTurnContext, undefined, { channelId: incomingChannelId, conversationId: params.conversationId })
|
|
115
|
+
if (!isDelegatedConversationState(conversationRefState)) {
|
|
116
|
+
res.status(403).send({ 'agent-response-auth-error': 'caller is not authorized for this conversation' })
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const callerClientId = req.user?.azp ?? req.user?.appid
|
|
121
|
+
const anonymousCaller = anonymousDevelopment && req.user?.name === 'anonymous'
|
|
122
|
+
const delegatedAgentMatches = anonymousCaller || (
|
|
123
|
+
typeof callerClientId === 'string' &&
|
|
124
|
+
callerClientId.length > 0 &&
|
|
125
|
+
callerClientId.toLowerCase() === conversationRefState.expectedAgentClientId.toLowerCase()
|
|
126
|
+
)
|
|
127
|
+
if (!delegatedAgentMatches) {
|
|
128
|
+
res.status(403).send({ 'agent-response-auth-error': 'caller is not authorized for this conversation' })
|
|
129
|
+
return
|
|
130
|
+
}
|
|
83
131
|
|
|
84
132
|
const callback = async (turnContext: TurnContext) => {
|
|
85
133
|
activity.applyConversationReference(conversationRefState.conversationReference)
|
|
@@ -88,7 +136,7 @@ export const createAgentResponseHandler = (
|
|
|
88
136
|
let response: unknown
|
|
89
137
|
let responseContentType: string | undefined
|
|
90
138
|
if (activity.type === ActivityTypes.EndOfConversation) {
|
|
91
|
-
await
|
|
139
|
+
await conversationState.delete(myTurnContext, { channelId: incomingChannelId, conversationId: params.conversationId })
|
|
92
140
|
|
|
93
141
|
applyActivityToTurnContext(turnContext, activity)
|
|
94
142
|
await agent.run(turnContext)
|
|
@@ -101,6 +149,7 @@ export const createAgentResponseHandler = (
|
|
|
101
149
|
} else {
|
|
102
150
|
response = await turnContext.sendActivity(activity)
|
|
103
151
|
}
|
|
152
|
+
|
|
104
153
|
if (responseContentType !== undefined) {
|
|
105
154
|
res.setHeader('content-type', responseContentType)
|
|
106
155
|
}
|
|
@@ -111,6 +160,18 @@ export const createAgentResponseHandler = (
|
|
|
111
160
|
}
|
|
112
161
|
}
|
|
113
162
|
|
|
163
|
+
function isDelegatedConversationState (state: ConversationReferenceState | undefined): state is DelegatedConversationState {
|
|
164
|
+
return typeof state?.conversationReference === 'object' &&
|
|
165
|
+
state.conversationReference !== null &&
|
|
166
|
+
typeof state.expectedAgentClientId === 'string' &&
|
|
167
|
+
state.expectedAgentClientId.trim().length > 0
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function emitAgentResponseWarning (message: string): void {
|
|
171
|
+
console.warn(`[agents:agent-client] ${message}`)
|
|
172
|
+
logger.warn(message)
|
|
173
|
+
}
|
|
174
|
+
|
|
114
175
|
const applyActivityToTurnContext = (turnContext: TurnContext, activity: Activity) => {
|
|
115
176
|
turnContext.activity.channelData = activity.channelData
|
|
116
177
|
turnContext.activity.code = activity.code
|
|
@@ -51,10 +51,7 @@ export function parseValueContinuation (value: unknown): string {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
interface ValueAction {
|
|
54
|
-
action:
|
|
55
|
-
type: string;
|
|
56
|
-
verb: string;
|
|
57
|
-
}
|
|
54
|
+
action: AdaptiveCardInvokeAction
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
/**
|
|
@@ -70,7 +67,7 @@ export function parseValueActionExecuteSelector (value: unknown): ValueAction |
|
|
|
70
67
|
const actionZodSchema = z.object({
|
|
71
68
|
type: z.string().min(1),
|
|
72
69
|
verb: z.string().min(1)
|
|
73
|
-
})
|
|
70
|
+
}).passthrough()
|
|
74
71
|
const actionValueExecuteSelector = z.object({
|
|
75
72
|
action: actionZodSchema
|
|
76
73
|
})
|
|
@@ -80,10 +77,7 @@ export function parseValueActionExecuteSelector (value: unknown): ValueAction |
|
|
|
80
77
|
}
|
|
81
78
|
const parsedValue = safeParsedValue.data
|
|
82
79
|
return {
|
|
83
|
-
action:
|
|
84
|
-
type: parsedValue.action.type,
|
|
85
|
-
verb: parsedValue.action.verb
|
|
86
|
-
}
|
|
80
|
+
action: parsedValue.action as unknown as AdaptiveCardInvokeAction
|
|
87
81
|
}
|
|
88
82
|
}
|
|
89
83
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Activity, ActivityTypes, ExceptionHelper } from '@microsoft/agents-activity'
|
|
7
|
+
import { debug } from '@microsoft/agents-telemetry'
|
|
7
8
|
import { AdaptiveCardInvokeResponse, AgentApplication, CardFactory, INVOKE_RESPONSE_KEY, InvokeResponse, MessageFactory, RouteSelector, TurnContext, TurnState } from '../../'
|
|
8
9
|
import { AdaptiveCardActionExecuteResponseType } from './adaptiveCardActionExecuteResponseType'
|
|
9
10
|
import { parseAdaptiveCardInvokeAction, parseValueActionExecuteSelector, parseValueDataset, parseValueSearchQuery } from './activityValueParsers'
|
|
@@ -16,6 +17,7 @@ export const ACTION_INVOKE_NAME = 'adaptiveCard/action'
|
|
|
16
17
|
const ACTION_EXECUTE_TYPE = 'Action.Execute'
|
|
17
18
|
const DEFAULT_ACTION_SUBMIT_FILTER = 'verb'
|
|
18
19
|
const SEARCH_INVOKE_NAME = 'application/search'
|
|
20
|
+
const logger = debug('agents:adaptive-cards')
|
|
19
21
|
|
|
20
22
|
enum AdaptiveCardInvokeResponseType {
|
|
21
23
|
/**
|
|
@@ -100,12 +102,30 @@ export class AdaptiveCardsActions<TState extends TurnState> {
|
|
|
100
102
|
* @param verb - A string, RegExp, RouteSelector, or an array of these to match the action verb.
|
|
101
103
|
* @param handler - A function to handle the action execution.
|
|
102
104
|
* @returns The Teams application instance.
|
|
105
|
+
*
|
|
106
|
+
* @remarks
|
|
107
|
+
* The delivery behavior depends on the action trigger and
|
|
108
|
+
* `adaptiveCardsOptions.actionExecuteResponseType`:
|
|
109
|
+
*
|
|
110
|
+
* | Response type | Automatic trigger | Manual trigger |
|
|
111
|
+
* | --- | --- | --- |
|
|
112
|
+
* | `REPLACE_FOR_INTERACTOR` | User response | User response |
|
|
113
|
+
* | `REPLACE_FOR_ALL` | User response | Shared update |
|
|
114
|
+
* | `NEW_MESSAGE_FOR_ALL` | User response | New message |
|
|
115
|
+
*
|
|
116
|
+
* - **User response** — Return an Adaptive Card invoke response only to the requesting or interacting user.
|
|
117
|
+
* - **Shared update** — Update the shared activity for all users and return an Adaptive Card invoke response to the interacting user.
|
|
118
|
+
* - **New message** — Send a new activity for all users and return a message acknowledgement to the interacting user.
|
|
119
|
+
*
|
|
120
|
+
* Automatic refresh actions always return only an Adaptive Card invoke response.
|
|
121
|
+
* Updating or sending a refreshable card while processing an automatic refresh
|
|
122
|
+
* would cause Teams to invoke the refresh action again.
|
|
103
123
|
*/
|
|
104
124
|
public actionExecute<TData = Record<string, any>>(
|
|
105
125
|
verb: string | RegExp | RouteSelector | (string | RegExp | RouteSelector)[],
|
|
106
126
|
handler: (context: TurnContext, state: TState, data: TData) => Promise<AdaptiveCard | string>
|
|
107
127
|
): AgentApplication<TState> {
|
|
108
|
-
|
|
128
|
+
const actionExecuteResponseType = this._app.options.adaptiveCardsOptions?.actionExecuteResponseType ?? AdaptiveCardActionExecuteResponseType.REPLACE_FOR_INTERACTOR;
|
|
109
129
|
(Array.isArray(verb) ? verb : [verb]).forEach((v) => {
|
|
110
130
|
const selector = createActionExecuteSelector(v)
|
|
111
131
|
this._app.addRoute(
|
|
@@ -113,6 +133,7 @@ export class AdaptiveCardsActions<TState extends TurnState> {
|
|
|
113
133
|
async (context, state) => {
|
|
114
134
|
const a = context?.activity
|
|
115
135
|
const invokeAction = parseValueActionExecuteSelector(a.value)
|
|
136
|
+
const isAutomaticRefresh = (a.value as { trigger?: string } | undefined)?.trigger === 'automatic'
|
|
116
137
|
if (
|
|
117
138
|
a?.type !== ActivityTypes.Invoke ||
|
|
118
139
|
a?.name !== ACTION_INVOKE_NAME ||
|
|
@@ -121,13 +142,11 @@ export class AdaptiveCardsActions<TState extends TurnState> {
|
|
|
121
142
|
throw ExceptionHelper.generateException(Error, Errors.UnexpectedActionExecute, undefined, { activityType: invokeAction?.action.type ?? 'undefined' })
|
|
122
143
|
}
|
|
123
144
|
|
|
124
|
-
if (invokeAction.action.verb !== v) {
|
|
125
|
-
|
|
126
|
-
console.log(`AdaptiveCards.actionExecute() triggered for verb: ${invokeAction.action.verb} does not match expected verb: ${v}`)
|
|
145
|
+
if (typeof v === 'string' && invokeAction.action.verb !== v) {
|
|
146
|
+
logger.warn(`AdaptiveCards.actionExecute() triggered for verb: ${invokeAction.action.verb} does not match expected verb: ${v}`)
|
|
127
147
|
}
|
|
128
148
|
|
|
129
|
-
|
|
130
|
-
const result = await handler(context, state, ((a.value as any).action as TData) ?? {} as TData)
|
|
149
|
+
const result = await handler(context, state, (invokeAction.action as unknown as TData) ?? {} as TData)
|
|
131
150
|
if (!context.turnState.get(INVOKE_RESPONSE_KEY)) {
|
|
132
151
|
let response: AdaptiveCardInvokeResponse
|
|
133
152
|
if (typeof result === 'string') {
|
|
@@ -138,20 +157,14 @@ export class AdaptiveCardsActions<TState extends TurnState> {
|
|
|
138
157
|
}
|
|
139
158
|
await sendInvokeResponse(context, response)
|
|
140
159
|
} else {
|
|
141
|
-
if (
|
|
142
|
-
result.refresh &&
|
|
143
|
-
actionExecuteResponseType !== AdaptiveCardActionExecuteResponseType.NEW_MESSAGE_FOR_ALL
|
|
144
|
-
) {
|
|
145
|
-
actionExecuteResponseType = AdaptiveCardActionExecuteResponseType.REPLACE_FOR_ALL
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const activity = MessageFactory.attachment(CardFactory.adaptiveCard(result))
|
|
149
160
|
response = {
|
|
150
161
|
statusCode: 200,
|
|
151
162
|
type: AdaptiveCardInvokeResponseType.ADAPTIVE,
|
|
152
163
|
value: result
|
|
153
164
|
}
|
|
154
|
-
if (
|
|
165
|
+
if (isAutomaticRefresh) {
|
|
166
|
+
await sendInvokeResponse(context, response)
|
|
167
|
+
} else if (
|
|
155
168
|
actionExecuteResponseType === AdaptiveCardActionExecuteResponseType.NEW_MESSAGE_FOR_ALL
|
|
156
169
|
) {
|
|
157
170
|
await sendInvokeResponse(context, {
|
|
@@ -159,10 +172,11 @@ export class AdaptiveCardsActions<TState extends TurnState> {
|
|
|
159
172
|
type: AdaptiveCardInvokeResponseType.MESSAGE,
|
|
160
173
|
value: 'Your response was sent to the app' as any
|
|
161
174
|
})
|
|
162
|
-
await context.sendActivity(
|
|
175
|
+
await context.sendActivity(MessageFactory.attachment(CardFactory.adaptiveCard(result)))
|
|
163
176
|
} else if (
|
|
164
177
|
actionExecuteResponseType === AdaptiveCardActionExecuteResponseType.REPLACE_FOR_ALL
|
|
165
178
|
) {
|
|
179
|
+
const activity = MessageFactory.attachment(CardFactory.adaptiveCard(result))
|
|
166
180
|
activity.id = context.activity.replyToId
|
|
167
181
|
await context.updateActivity(activity)
|
|
168
182
|
await sendInvokeResponse(context, response)
|
|
@@ -270,12 +284,15 @@ function createActionExecuteSelector (verb: string | RegExp | RouteSelector): Ro
|
|
|
270
284
|
} else if (verb instanceof RegExp) {
|
|
271
285
|
return (context: TurnContext) => {
|
|
272
286
|
const a = context?.activity
|
|
273
|
-
const valueAction = parseValueActionExecuteSelector(a.value)
|
|
274
287
|
const isInvoke =
|
|
275
288
|
a?.type === ActivityTypes.Invoke &&
|
|
276
|
-
a?.name === ACTION_INVOKE_NAME
|
|
277
|
-
|
|
278
|
-
|
|
289
|
+
a?.name === ACTION_INVOKE_NAME
|
|
290
|
+
if (!isInvoke) {
|
|
291
|
+
return Promise.resolve(false)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const valueAction = tryParseActionExecuteSelectorValue(a.value)
|
|
295
|
+
if (valueAction?.action?.type === ACTION_EXECUTE_TYPE && typeof valueAction.action.verb === 'string') {
|
|
279
296
|
return Promise.resolve(verb.test(valueAction.action.verb))
|
|
280
297
|
} else {
|
|
281
298
|
return Promise.resolve(false)
|
|
@@ -284,12 +301,15 @@ function createActionExecuteSelector (verb: string | RegExp | RouteSelector): Ro
|
|
|
284
301
|
} else {
|
|
285
302
|
return (context: TurnContext) => {
|
|
286
303
|
const a = context?.activity
|
|
287
|
-
const valueAction = parseValueActionExecuteSelector(a.value)
|
|
288
304
|
const isInvoke =
|
|
289
305
|
a?.type === ActivityTypes.Invoke &&
|
|
290
|
-
a?.name === ACTION_INVOKE_NAME
|
|
291
|
-
|
|
292
|
-
|
|
306
|
+
a?.name === ACTION_INVOKE_NAME
|
|
307
|
+
if (!isInvoke) {
|
|
308
|
+
return Promise.resolve(false)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const valueAction = tryParseActionExecuteSelectorValue(a.value)
|
|
312
|
+
if (valueAction?.action?.type === ACTION_EXECUTE_TYPE && valueAction.action?.verb === verb) {
|
|
293
313
|
return Promise.resolve(true)
|
|
294
314
|
} else {
|
|
295
315
|
return Promise.resolve(false)
|
|
@@ -298,6 +318,14 @@ function createActionExecuteSelector (verb: string | RegExp | RouteSelector): Ro
|
|
|
298
318
|
}
|
|
299
319
|
}
|
|
300
320
|
|
|
321
|
+
function tryParseActionExecuteSelectorValue (value: unknown): ReturnType<typeof parseValueActionExecuteSelector> {
|
|
322
|
+
try {
|
|
323
|
+
return parseValueActionExecuteSelector(value)
|
|
324
|
+
} catch {
|
|
325
|
+
return undefined
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
301
329
|
function createActionSubmitSelector (verb: string | RegExp | RouteSelector, filter: string): RouteSelector {
|
|
302
330
|
if (typeof verb === 'function') {
|
|
303
331
|
return verb
|
|
@@ -36,6 +36,7 @@ const TYPING_TIMER_STATE_KEY = Symbol('typingTimerState')
|
|
|
36
36
|
type TypingTimerState = {
|
|
37
37
|
timer?: NodeJS.Timeout
|
|
38
38
|
lastSend: Promise<unknown>
|
|
39
|
+
notificationSequence: number
|
|
39
40
|
stop: () => void
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -678,7 +679,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
678
679
|
|
|
679
680
|
if (!authorized) {
|
|
680
681
|
const managed = trace(AgentApplicationTraceDefinitions.run)
|
|
681
|
-
managed.record({ authorized, activity: context.activity })
|
|
682
|
+
managed.record({ authorized, activity: Activity.fromObject(context.activity) })
|
|
682
683
|
managed.end()
|
|
683
684
|
// We don't log a message here because it is handled by the authorization manager and could cause confusion during mid sign-in operations.
|
|
684
685
|
return false
|
|
@@ -719,84 +720,90 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
719
720
|
* Executes the turn processing logic for the given context, including routing and handler execution.
|
|
720
721
|
*/
|
|
721
722
|
private async runTurn (context: TurnContext): Promise<boolean> {
|
|
722
|
-
|
|
723
|
-
|
|
723
|
+
const managed = trace(AgentApplicationTraceDefinitions.run)
|
|
724
|
+
managed.record({ authorized: true, activity: Activity.fromObject(context.activity) })
|
|
724
725
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
726
|
+
try {
|
|
727
|
+
if (this._authorizationManager.handlers.length > 0) {
|
|
728
|
+
context.turnState.set(AgentApplication.UserAuthorizationKey, this._authorization)
|
|
729
|
+
}
|
|
729
730
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
731
|
+
const connections = this._options.connections ?? this._adapter?.connectionManager
|
|
732
|
+
if (connections) {
|
|
733
|
+
context.turnState.set(AgentApplication.ConnectionsKey, connections)
|
|
734
|
+
}
|
|
734
735
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
736
|
+
if (this._options.startTypingTimer) {
|
|
737
|
+
this.startTypingTimer(context)
|
|
738
|
+
}
|
|
738
739
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
740
|
+
if (this._options.removeRecipientMention && context.activity.type === ActivityTypes.Message) {
|
|
741
|
+
context.activity.removeRecipientMention()
|
|
742
|
+
}
|
|
742
743
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
744
|
+
if (this._options.normalizeMentions && context.activity.type === ActivityTypes.Message) {
|
|
745
|
+
context.activity.normalizeMentions()
|
|
746
|
+
}
|
|
746
747
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
748
|
+
const { storage, turnStateFactory } = this._options
|
|
749
|
+
const state = turnStateFactory()
|
|
750
|
+
await state.load(context, storage)
|
|
750
751
|
|
|
751
|
-
|
|
752
|
+
const route = await this.getRoute(context)
|
|
752
753
|
|
|
753
|
-
|
|
754
|
+
managed.record({ routeMatched: route !== undefined })
|
|
754
755
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
756
|
+
if (!route) {
|
|
757
|
+
logger.debug('No matching route found for activity:', context.activity)
|
|
758
|
+
return false
|
|
759
|
+
}
|
|
759
760
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
761
|
+
const fileDownloaders = this._options.fileDownloaders
|
|
762
|
+
if (Array.isArray(fileDownloaders) && fileDownloaders.length > 0) {
|
|
763
|
+
await managed.child(AgentApplicationTraceDefinitions.downloadFiles, async ({ record }) => {
|
|
764
|
+
record({ attachmentsCount: context.activity.attachments?.length })
|
|
765
|
+
for (let i = 0; i < fileDownloaders.length; i++) {
|
|
766
|
+
await fileDownloaders[i].downloadAndStoreFiles(context, state)
|
|
767
|
+
}
|
|
768
|
+
})
|
|
769
|
+
}
|
|
769
770
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
})
|
|
777
|
-
}
|
|
771
|
+
if (this._beforeTurn.length > 0) {
|
|
772
|
+
const continueTurn = await managed.child(AgentApplicationTraceDefinitions.beforeTurn, async () => {
|
|
773
|
+
if (!(await this.callEventHandlers(context, state, this._beforeTurn))) {
|
|
774
|
+
await state.save(context, storage)
|
|
775
|
+
return false
|
|
776
|
+
}
|
|
778
777
|
|
|
779
|
-
|
|
780
|
-
record({ isInvoke: route.isInvokeRoute, isAgentic: route.isAgenticRoute })
|
|
781
|
-
await route.handler(context, state)
|
|
778
|
+
return true
|
|
782
779
|
})
|
|
783
780
|
|
|
784
|
-
if (
|
|
785
|
-
|
|
786
|
-
if (await this.callEventHandlers(context, state, this._afterTurn)) {
|
|
787
|
-
await state.save(context, storage)
|
|
788
|
-
}
|
|
789
|
-
})
|
|
781
|
+
if (!continueTurn) {
|
|
782
|
+
return false
|
|
790
783
|
}
|
|
784
|
+
}
|
|
791
785
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
786
|
+
await managed.child(AgentApplicationTraceDefinitions.routeHandler, async ({ record }) => {
|
|
787
|
+
record({ isInvoke: route.isInvokeRoute, isAgentic: route.isAgenticRoute })
|
|
788
|
+
await route.handler(context, state)
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
if (this._afterTurn.length > 0) {
|
|
792
|
+
await managed.child(AgentApplicationTraceDefinitions.afterTurn, async () => {
|
|
793
|
+
if (await this.callEventHandlers(context, state, this._afterTurn)) {
|
|
794
|
+
await state.save(context, storage)
|
|
795
|
+
}
|
|
796
|
+
})
|
|
798
797
|
}
|
|
799
|
-
|
|
798
|
+
|
|
799
|
+
return true
|
|
800
|
+
} catch (err: any) {
|
|
801
|
+
logger.error(err)
|
|
802
|
+
throw managed.fail(err)
|
|
803
|
+
} finally {
|
|
804
|
+
this.stopTypingTimer(context)
|
|
805
|
+
managed.end()
|
|
806
|
+
}
|
|
800
807
|
}
|
|
801
808
|
|
|
802
809
|
/**
|
|
@@ -882,6 +889,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
882
889
|
|
|
883
890
|
const state: TypingTimerState = {
|
|
884
891
|
lastSend: Promise.resolve(),
|
|
892
|
+
notificationSequence: 0,
|
|
885
893
|
stop: () => {
|
|
886
894
|
if (state.timer) {
|
|
887
895
|
clearTimeout(state.timer)
|
|
@@ -909,7 +917,8 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
909
917
|
|
|
910
918
|
const onTimeout = async () => {
|
|
911
919
|
try {
|
|
912
|
-
state.
|
|
920
|
+
state.notificationSequence += 1
|
|
921
|
+
state.lastSend = this.sendTypingActivity(context, state.notificationSequence)
|
|
913
922
|
await state.lastSend
|
|
914
923
|
} catch (err: any) {
|
|
915
924
|
logger.error(err)
|
|
@@ -942,11 +951,19 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
942
951
|
return streamingEntity?.streamType ?? activity.channelData?.streamType
|
|
943
952
|
}
|
|
944
953
|
|
|
945
|
-
private async sendTypingActivity (context: TurnContext): Promise<ResourceResponse[] | undefined> {
|
|
954
|
+
private async sendTypingActivity (context: TurnContext, notificationSequence: number): Promise<ResourceResponse[] | undefined> {
|
|
946
955
|
const conversationReference = context.activity.getConversationReference()
|
|
947
956
|
const typingActivity = Activity.fromObject({ type: ActivityTypes.Typing }).applyConversationReference(conversationReference)
|
|
948
957
|
|
|
949
|
-
return await
|
|
958
|
+
return await trace(AgentApplicationTraceDefinitions.typingIndicator, ({ record }) => {
|
|
959
|
+
record({
|
|
960
|
+
activityType: typingActivity.type,
|
|
961
|
+
channelId: typingActivity.channelId,
|
|
962
|
+
conversationId: typingActivity.conversation?.id,
|
|
963
|
+
notificationSequence,
|
|
964
|
+
})
|
|
965
|
+
return context.adapter.sendActivities(context, [typingActivity])
|
|
966
|
+
})
|
|
950
967
|
}
|
|
951
968
|
|
|
952
969
|
/**
|
|
@@ -1093,22 +1110,77 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
1093
1110
|
handler: (context: TurnContext) => Promise<any>
|
|
1094
1111
|
) {
|
|
1095
1112
|
const activity = Activity.fromObject(context.activity)
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1113
|
+
const adapter = context.adapter
|
|
1114
|
+
const identity = context.identity
|
|
1115
|
+
const createErrorContext = () => new TurnContext(adapter, Activity.fromObject(activity), identity)
|
|
1116
|
+
|
|
1117
|
+
try {
|
|
1118
|
+
const reference = activity.getConversationReference()
|
|
1119
|
+
this.continueConversationAsync(identity, reference, async (ctx) => {
|
|
1120
|
+
try {
|
|
1121
|
+
Object.assign(ctx.activity, activity)
|
|
1122
|
+
await handler(ctx)
|
|
1123
|
+
} catch (err) {
|
|
1124
|
+
await this.handleLongRunningCallError(ctx, activity, err)
|
|
1105
1125
|
}
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1126
|
+
}).catch(async err => {
|
|
1127
|
+
await this.handleLongRunningCallError(createErrorContext(), activity, err)
|
|
1128
|
+
})
|
|
1129
|
+
} catch (err) {
|
|
1130
|
+
this.handleLongRunningCallError(createErrorContext(), activity, err).catch(handlerErr => {
|
|
1131
|
+
logger.error(`Unhandled error in long-running error handler for activity '${activity.type}' (id: ${activity.id}):`, this.normalizeLongRunningCallError(handlerErr))
|
|
1132
|
+
})
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
private async handleLongRunningCallError (
|
|
1137
|
+
context: TurnContext,
|
|
1138
|
+
activity: Activity,
|
|
1139
|
+
err: unknown
|
|
1140
|
+
): Promise<void> {
|
|
1141
|
+
const error = this.normalizeLongRunningCallError(err)
|
|
1142
|
+
|
|
1143
|
+
try {
|
|
1144
|
+
await context.adapter.onTurnError(context, error)
|
|
1145
|
+
} catch (handlerErr) {
|
|
1146
|
+
logger.error(`Unhandled error in long-running error handler for activity '${activity.type}' (id: ${activity.id}):`, this.normalizeLongRunningCallError(handlerErr))
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
private normalizeLongRunningCallError (err: unknown): Error {
|
|
1151
|
+
if (err instanceof Error) {
|
|
1152
|
+
return err
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
return ExceptionHelper.generateException(Error, Errors.UnknownErrorType, undefined, {
|
|
1156
|
+
errorMessage: this.stringifyThrownValue(err)
|
|
1109
1157
|
})
|
|
1110
1158
|
}
|
|
1111
1159
|
|
|
1160
|
+
private stringifyThrownValue (value: unknown): string {
|
|
1161
|
+
if (typeof value === 'string') {
|
|
1162
|
+
return value
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
if (value === undefined) {
|
|
1166
|
+
return 'undefined'
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
if (value === null) {
|
|
1170
|
+
return 'null'
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
if (typeof value === 'object') {
|
|
1174
|
+
try {
|
|
1175
|
+
return JSON.stringify(value) ?? Object.prototype.toString.call(value)
|
|
1176
|
+
} catch {
|
|
1177
|
+
return Object.prototype.toString.call(value)
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
return String(value)
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1112
1184
|
/**
|
|
1113
1185
|
* Creates a selector function for activity types.
|
|
1114
1186
|
*
|