@microsoft/agents-hosting 1.7.1 → 1.8.0-beta.34.g5d543f96c0
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 -6
- 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 +7 -0
- package/dist/src/app/agentApplication.js +130 -70
- 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.js +2 -9
- 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 +3 -1
- package/dist/src/auth/index.js +3 -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.js +3 -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 -10
- package/src/app/adaptiveCards/adaptiveCardsActions.ts +52 -24
- package/src/app/agentApplication.ts +143 -67
- 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 +2 -8
- package/src/app/teamsAttachmentDownloader.ts +11 -1
- package/src/auth/authConfiguration.ts +12 -0
- package/src/auth/index.ts +3 -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 +2 -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
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import { z } from 'zod'
|
|
7
7
|
import { activityZodSchema, AdaptiveCardInvokeAction, adaptiveCardInvokeActionZodSchema, ExceptionHelper } from '@microsoft/agents-activity'
|
|
8
|
-
// import { MessagingExtensionQuery, messagingExtensionQueryZodSchema } from '../messageExtension/messagingExtensionQuery'
|
|
9
8
|
import { adaptiveCardsSearchParamsZodSchema } from './adaptiveCardsSearchParams'
|
|
10
9
|
import { Errors } from '../../errorHelper'
|
|
11
10
|
|
|
@@ -52,10 +51,7 @@ export function parseValueContinuation (value: unknown): string {
|
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
interface ValueAction {
|
|
55
|
-
action:
|
|
56
|
-
type: string;
|
|
57
|
-
verb: string;
|
|
58
|
-
}
|
|
54
|
+
action: AdaptiveCardInvokeAction
|
|
59
55
|
}
|
|
60
56
|
|
|
61
57
|
/**
|
|
@@ -71,7 +67,7 @@ export function parseValueActionExecuteSelector (value: unknown): ValueAction |
|
|
|
71
67
|
const actionZodSchema = z.object({
|
|
72
68
|
type: z.string().min(1),
|
|
73
69
|
verb: z.string().min(1)
|
|
74
|
-
})
|
|
70
|
+
}).passthrough()
|
|
75
71
|
const actionValueExecuteSelector = z.object({
|
|
76
72
|
action: actionZodSchema
|
|
77
73
|
})
|
|
@@ -81,10 +77,7 @@ export function parseValueActionExecuteSelector (value: unknown): ValueAction |
|
|
|
81
77
|
}
|
|
82
78
|
const parsedValue = safeParsedValue.data
|
|
83
79
|
return {
|
|
84
|
-
action:
|
|
85
|
-
type: parsedValue.action.type,
|
|
86
|
-
verb: parsedValue.action.verb
|
|
87
|
-
}
|
|
80
|
+
action: parsedValue.action as unknown as AdaptiveCardInvokeAction
|
|
88
81
|
}
|
|
89
82
|
}
|
|
90
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
|
|
@@ -87,6 +87,12 @@ export type ApplicationEventHandler<TState extends TurnState> = (context: TurnCo
|
|
|
87
87
|
*
|
|
88
88
|
*/
|
|
89
89
|
export class AgentApplication<TState extends TurnState> {
|
|
90
|
+
/** Turn-state key for the user authorization service configured on this application. */
|
|
91
|
+
public static readonly UserAuthorizationKey = Symbol('UserAuthorization')
|
|
92
|
+
|
|
93
|
+
/** Turn-state key for the token connections configured on this application. */
|
|
94
|
+
public static readonly ConnectionsKey = Symbol('Connections')
|
|
95
|
+
|
|
90
96
|
protected readonly _options: AgentApplicationOptions<TState>
|
|
91
97
|
protected readonly _routes: RouteList<TState> = new RouteList<TState>()
|
|
92
98
|
protected readonly _beforeTurn: ApplicationEventHandler<TState>[] = []
|
|
@@ -672,7 +678,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
672
678
|
|
|
673
679
|
if (!authorized) {
|
|
674
680
|
const managed = trace(AgentApplicationTraceDefinitions.run)
|
|
675
|
-
managed.record({ authorized, activity: context.activity })
|
|
681
|
+
managed.record({ authorized, activity: Activity.fromObject(context.activity) })
|
|
676
682
|
managed.end()
|
|
677
683
|
// We don't log a message here because it is handled by the authorization manager and could cause confusion during mid sign-in operations.
|
|
678
684
|
return false
|
|
@@ -713,75 +719,90 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
713
719
|
* Executes the turn processing logic for the given context, including routing and handler execution.
|
|
714
720
|
*/
|
|
715
721
|
private async runTurn (context: TurnContext): Promise<boolean> {
|
|
716
|
-
|
|
717
|
-
|
|
722
|
+
const managed = trace(AgentApplicationTraceDefinitions.run)
|
|
723
|
+
managed.record({ authorized: true, activity: Activity.fromObject(context.activity) })
|
|
718
724
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
725
|
+
try {
|
|
726
|
+
if (this._authorizationManager.handlers.length > 0) {
|
|
727
|
+
context.turnState.set(AgentApplication.UserAuthorizationKey, this._authorization)
|
|
728
|
+
}
|
|
723
729
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
730
|
+
const connections = this._options.connections ?? this._adapter?.connectionManager
|
|
731
|
+
if (connections) {
|
|
732
|
+
context.turnState.set(AgentApplication.ConnectionsKey, connections)
|
|
733
|
+
}
|
|
727
734
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
735
|
+
if (this._options.startTypingTimer) {
|
|
736
|
+
this.startTypingTimer(context)
|
|
737
|
+
}
|
|
731
738
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
739
|
+
if (this._options.removeRecipientMention && context.activity.type === ActivityTypes.Message) {
|
|
740
|
+
context.activity.removeRecipientMention()
|
|
741
|
+
}
|
|
735
742
|
|
|
736
|
-
|
|
743
|
+
if (this._options.normalizeMentions && context.activity.type === ActivityTypes.Message) {
|
|
744
|
+
context.activity.normalizeMentions()
|
|
745
|
+
}
|
|
737
746
|
|
|
738
|
-
|
|
747
|
+
const { storage, turnStateFactory } = this._options
|
|
748
|
+
const state = turnStateFactory()
|
|
749
|
+
await state.load(context, storage)
|
|
739
750
|
|
|
740
|
-
|
|
741
|
-
logger.debug('No matching route found for activity:', context.activity)
|
|
742
|
-
return false
|
|
743
|
-
}
|
|
751
|
+
const route = await this.getRoute(context)
|
|
744
752
|
|
|
745
|
-
|
|
746
|
-
if (Array.isArray(fileDownloaders) && fileDownloaders.length > 0) {
|
|
747
|
-
await trace(AgentApplicationTraceDefinitions.downloadFiles, async ({ record }) => {
|
|
748
|
-
record({ attachmentsCount: context.activity.attachments?.length })
|
|
749
|
-
for (let i = 0; i < fileDownloaders.length; i++) {
|
|
750
|
-
await fileDownloaders[i].downloadAndStoreFiles(context, state)
|
|
751
|
-
}
|
|
752
|
-
})
|
|
753
|
-
}
|
|
753
|
+
managed.record({ routeMatched: route !== undefined })
|
|
754
754
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
return false
|
|
760
|
-
}
|
|
761
|
-
})
|
|
762
|
-
}
|
|
755
|
+
if (!route) {
|
|
756
|
+
logger.debug('No matching route found for activity:', context.activity)
|
|
757
|
+
return false
|
|
758
|
+
}
|
|
763
759
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
760
|
+
const fileDownloaders = this._options.fileDownloaders
|
|
761
|
+
if (Array.isArray(fileDownloaders) && fileDownloaders.length > 0) {
|
|
762
|
+
await managed.child(AgentApplicationTraceDefinitions.downloadFiles, async ({ record }) => {
|
|
763
|
+
record({ attachmentsCount: context.activity.attachments?.length })
|
|
764
|
+
for (let i = 0; i < fileDownloaders.length; i++) {
|
|
765
|
+
await fileDownloaders[i].downloadAndStoreFiles(context, state)
|
|
766
|
+
}
|
|
767
767
|
})
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (this._beforeTurn.length > 0) {
|
|
771
|
+
const continueTurn = await managed.child(AgentApplicationTraceDefinitions.beforeTurn, async () => {
|
|
772
|
+
if (!(await this.callEventHandlers(context, state, this._beforeTurn))) {
|
|
773
|
+
await state.save(context, storage)
|
|
774
|
+
return false
|
|
775
|
+
}
|
|
768
776
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
})
|
|
777
|
+
return true
|
|
778
|
+
})
|
|
779
|
+
|
|
780
|
+
if (!continueTurn) {
|
|
781
|
+
return false
|
|
775
782
|
}
|
|
783
|
+
}
|
|
776
784
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
785
|
+
await managed.child(AgentApplicationTraceDefinitions.routeHandler, async ({ record }) => {
|
|
786
|
+
record({ isInvoke: route.isInvokeRoute, isAgentic: route.isAgenticRoute })
|
|
787
|
+
await route.handler(context, state)
|
|
788
|
+
})
|
|
789
|
+
|
|
790
|
+
if (this._afterTurn.length > 0) {
|
|
791
|
+
await managed.child(AgentApplicationTraceDefinitions.afterTurn, async () => {
|
|
792
|
+
if (await this.callEventHandlers(context, state, this._afterTurn)) {
|
|
793
|
+
await state.save(context, storage)
|
|
794
|
+
}
|
|
795
|
+
})
|
|
783
796
|
}
|
|
784
|
-
|
|
797
|
+
|
|
798
|
+
return true
|
|
799
|
+
} catch (err: any) {
|
|
800
|
+
logger.error(err)
|
|
801
|
+
throw managed.fail(err)
|
|
802
|
+
} finally {
|
|
803
|
+
this.stopTypingTimer(context)
|
|
804
|
+
managed.end()
|
|
805
|
+
}
|
|
785
806
|
}
|
|
786
807
|
|
|
787
808
|
/**
|
|
@@ -1078,22 +1099,77 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
1078
1099
|
handler: (context: TurnContext) => Promise<any>
|
|
1079
1100
|
) {
|
|
1080
1101
|
const activity = Activity.fromObject(context.activity)
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1102
|
+
const adapter = context.adapter
|
|
1103
|
+
const identity = context.identity
|
|
1104
|
+
const createErrorContext = () => new TurnContext(adapter, Activity.fromObject(activity), identity)
|
|
1105
|
+
|
|
1106
|
+
try {
|
|
1107
|
+
const reference = activity.getConversationReference()
|
|
1108
|
+
this.continueConversationAsync(identity, reference, async (ctx) => {
|
|
1109
|
+
try {
|
|
1110
|
+
Object.assign(ctx.activity, activity)
|
|
1111
|
+
await handler(ctx)
|
|
1112
|
+
} catch (err) {
|
|
1113
|
+
await this.handleLongRunningCallError(ctx, activity, err)
|
|
1090
1114
|
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1115
|
+
}).catch(async err => {
|
|
1116
|
+
await this.handleLongRunningCallError(createErrorContext(), activity, err)
|
|
1117
|
+
})
|
|
1118
|
+
} catch (err) {
|
|
1119
|
+
this.handleLongRunningCallError(createErrorContext(), activity, err).catch(handlerErr => {
|
|
1120
|
+
logger.error(`Unhandled error in long-running error handler for activity '${activity.type}' (id: ${activity.id}):`, this.normalizeLongRunningCallError(handlerErr))
|
|
1121
|
+
})
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
private async handleLongRunningCallError (
|
|
1126
|
+
context: TurnContext,
|
|
1127
|
+
activity: Activity,
|
|
1128
|
+
err: unknown
|
|
1129
|
+
): Promise<void> {
|
|
1130
|
+
const error = this.normalizeLongRunningCallError(err)
|
|
1131
|
+
|
|
1132
|
+
try {
|
|
1133
|
+
await context.adapter.onTurnError(context, error)
|
|
1134
|
+
} catch (handlerErr) {
|
|
1135
|
+
logger.error(`Unhandled error in long-running error handler for activity '${activity.type}' (id: ${activity.id}):`, this.normalizeLongRunningCallError(handlerErr))
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
private normalizeLongRunningCallError (err: unknown): Error {
|
|
1140
|
+
if (err instanceof Error) {
|
|
1141
|
+
return err
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
return ExceptionHelper.generateException(Error, Errors.UnknownErrorType, undefined, {
|
|
1145
|
+
errorMessage: this.stringifyThrownValue(err)
|
|
1094
1146
|
})
|
|
1095
1147
|
}
|
|
1096
1148
|
|
|
1149
|
+
private stringifyThrownValue (value: unknown): string {
|
|
1150
|
+
if (typeof value === 'string') {
|
|
1151
|
+
return value
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
if (value === undefined) {
|
|
1155
|
+
return 'undefined'
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
if (value === null) {
|
|
1159
|
+
return 'null'
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
if (typeof value === 'object') {
|
|
1163
|
+
try {
|
|
1164
|
+
return JSON.stringify(value) ?? Object.prototype.toString.call(value)
|
|
1165
|
+
} catch {
|
|
1166
|
+
return Object.prototype.toString.call(value)
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
return String(value)
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1097
1173
|
/**
|
|
1098
1174
|
* Creates a selector function for activity types.
|
|
1099
1175
|
*
|